dimine-python-sdk 0.2.6__py3-none-any.whl → 0.2.8__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.
- dimine_python_sdk/__init__.py +1 -1
- dimine_python_sdk/cli/__init__.py +33 -0
- dimine_python_sdk/cli/commands/app.py +318 -0
- dimine_python_sdk/cli/commands/file.py +24 -4
- dimine_python_sdk/cli/commands/layer.py +9 -8
- dimine_python_sdk/conn/base_conn_client.py +2 -2
- dimine_python_sdk/conn/conn_client.py +48 -9
- dimine_python_sdk/docs/examples/cli_usage.md +16 -1
- dimine_python_sdk/docs/examples/open_pit_blasting_design.md +227 -0
- dimine_python_sdk/docs/examples/summary.md +1 -0
- dimine_python_sdk/docs/guides/cli.md +55 -3
- dimine_python_sdk/docs/guides/lib/SUMMARY.md +1 -0
- dimine_python_sdk/docs/guides/lib/blasting/SUMMARY.md +9 -0
- dimine_python_sdk/docs/guides/lib/blasting/charge_from_template.md +188 -0
- dimine_python_sdk/docs/guides/lib/blasting/config_databases.md +279 -0
- dimine_python_sdk/docs/guides/lib/blasting/design_flow.md +305 -0
- dimine_python_sdk/docs/guides/lib/blasting/overview.md +286 -0
- dimine_python_sdk/docs/guides/lib/io/dmg_file.md +11 -1
- dimine_python_sdk/docs/guides/lib/io/dmo_file.md +233 -159
- dimine_python_sdk/lib/blasting/__init__.py +171 -0
- dimine_python_sdk/lib/blasting/analysis.py +61 -0
- dimine_python_sdk/lib/blasting/api.py +136 -0
- dimine_python_sdk/lib/blasting/charge_design.py +207 -0
- dimine_python_sdk/lib/blasting/charge_template.py +433 -0
- dimine_python_sdk/lib/blasting/defaults.py +104 -0
- dimine_python_sdk/lib/blasting/dmo_export.py +124 -0
- dimine_python_sdk/lib/blasting/errors.py +37 -0
- dimine_python_sdk/lib/blasting/explosive_db.py +423 -0
- dimine_python_sdk/lib/blasting/hole_layout.py +265 -0
- dimine_python_sdk/lib/blasting/hole_param.py +375 -0
- dimine_python_sdk/lib/blasting/models.py +354 -0
- dimine_python_sdk/lib/blasting/network.py +254 -0
- dimine_python_sdk/lib/blasting/report.py +95 -0
- dimine_python_sdk/lib/blasting/template_adapt.py +649 -0
- dimine_python_sdk/lib/io/dmg_file.py +31 -13
- dimine_python_sdk/lib/io/dmo_file.py +2039 -327
- dimine_python_sdk/lib/io/dmt_file.py +12 -3
- dimine_python_sdk/lib/native/data_table.py +25 -3
- dimine_python_sdk/lib/native/geometry.py +6 -0
- dimine_python_sdk/lib/prospecting/compositing.py +6 -1
- dimine_python_sdk/lib/prospecting/high_grade.py +18 -1
- dimine_python_sdk/logger.py +1 -1
- dimine_python_sdk/models/__init__.py +16 -1
- dimine_python_sdk/models/geometry.py +91 -0
- dimine_python_sdk/models/types.py +31 -76
- dimine_python_sdk/site/404.html +241 -13
- dimine_python_sdk/site/examples/cli_usage.html +280 -19
- dimine_python_sdk/site/examples/contour_modeling_visualization.html +241 -13
- dimine_python_sdk/site/examples/import_csv_to_drill_db.html +241 -13
- dimine_python_sdk/site/examples/lib_file_usage.html +241 -13
- dimine_python_sdk/site/examples/open_pit_blasting_design.html +2569 -0
- dimine_python_sdk/site/examples/reserve_estimation_pipeline.html +243 -13
- dimine_python_sdk/site/examples/summary.html +242 -13
- dimine_python_sdk/site/guides/SUMMARY.html +241 -13
- dimine_python_sdk/site/guides/cli.html +379 -17
- dimine_python_sdk/site/guides/conn/SUMMARY.html +241 -13
- dimine_python_sdk/site/guides/conn/data_edit.html +241 -13
- dimine_python_sdk/site/guides/conn/data_get.html +241 -13
- dimine_python_sdk/site/guides/conn/features.html +241 -13
- dimine_python_sdk/site/guides/conn/io.html +241 -13
- dimine_python_sdk/site/guides/lib/SUMMARY.html +243 -14
- dimine_python_sdk/site/guides/lib/blasting/SUMMARY.html +2177 -0
- dimine_python_sdk/site/guides/lib/blasting/charge_from_template.html +2588 -0
- dimine_python_sdk/site/guides/lib/blasting/config_databases.html +2955 -0
- dimine_python_sdk/site/guides/lib/blasting/design_flow.html +2815 -0
- dimine_python_sdk/site/guides/lib/blasting/overview.html +3322 -0
- dimine_python_sdk/site/guides/lib/exploitation/SUMMARY.html +245 -17
- dimine_python_sdk/site/guides/lib/exploitation/contour_modeling.html +244 -16
- dimine_python_sdk/site/guides/lib/exploitation/implicit_modeling.html +244 -16
- dimine_python_sdk/site/guides/lib/exploitation/laneway.html +244 -16
- dimine_python_sdk/site/guides/lib/io/SUMMARY.html +241 -13
- dimine_python_sdk/site/guides/lib/io/dmb_file.html +241 -13
- dimine_python_sdk/site/guides/lib/io/dmd_file.html +241 -13
- dimine_python_sdk/site/guides/lib/io/dmf_file.html +241 -13
- dimine_python_sdk/site/guides/lib/io/dmg_file.html +252 -14
- dimine_python_sdk/site/guides/lib/io/dmo_file.html +377 -78
- dimine_python_sdk/site/guides/lib/io/dmt_file.html +241 -13
- dimine_python_sdk/site/guides/lib/io/file_conversion.html +241 -13
- dimine_python_sdk/site/guides/lib/prospecting/SUMMARY.html +241 -13
- dimine_python_sdk/site/guides/lib/prospecting/drill_db.html +241 -13
- dimine_python_sdk/site/guides/lib/prospecting/reserve_estimation.html +241 -13
- dimine_python_sdk/site/guides/lib/visualization/SUMMARY.html +241 -13
- dimine_python_sdk/site/guides/lib/visualization/visualization.html +241 -13
- dimine_python_sdk/site/guides/models/SUMMARY.html +241 -13
- dimine_python_sdk/site/guides/models/data_types.html +241 -13
- dimine_python_sdk/site/index.html +241 -13
- dimine_python_sdk/site/objects.inv +0 -0
- dimine_python_sdk/site/quickstart.html +241 -13
- dimine_python_sdk/site/reference/SUMMARY.html +241 -13
- dimine_python_sdk/site/reference/cli/commands/camera.html +241 -13
- dimine_python_sdk/site/reference/cli/commands/convert.html +241 -13
- dimine_python_sdk/site/reference/cli/commands/datatable.html +241 -13
- dimine_python_sdk/site/reference/cli/commands/entity.html +241 -13
- dimine_python_sdk/site/reference/cli/commands/feature.html +241 -13
- dimine_python_sdk/site/reference/cli/commands/file.html +381 -123
- dimine_python_sdk/site/reference/cli/commands/geometry.html +241 -13
- dimine_python_sdk/site/reference/cli/commands/laneway.html +241 -13
- dimine_python_sdk/site/reference/cli/commands/layer.html +253 -23
- dimine_python_sdk/site/reference/cli/commands/text.html +241 -13
- dimine_python_sdk/site/reference/conn/base_conn_client.html +243 -17
- dimine_python_sdk/site/reference/conn/conn_client.html +886 -428
- dimine_python_sdk/site/reference/lib/bridge.html +580 -246
- dimine_python_sdk/site/reference/lib/exploitation/api.html +241 -13
- dimine_python_sdk/site/reference/lib/exploitation/contour_modeling.html +241 -13
- dimine_python_sdk/site/reference/lib/exploitation/implicit_modeling.html +241 -13
- dimine_python_sdk/site/reference/lib/exploitation/lane_way.html +241 -13
- dimine_python_sdk/site/reference/lib/exploitation/models.html +241 -13
- dimine_python_sdk/site/reference/lib/io/dmb_file.html +241 -13
- dimine_python_sdk/site/reference/lib/io/dmd_file.html +241 -13
- dimine_python_sdk/site/reference/lib/io/dmf_file.html +241 -13
- dimine_python_sdk/site/reference/lib/io/dmg_file.html +350 -70
- dimine_python_sdk/site/reference/lib/io/dmo_file.html +4394 -712
- dimine_python_sdk/site/reference/lib/io/dmt_file.html +393 -89
- dimine_python_sdk/site/reference/lib/io/file_converter.html +241 -13
- dimine_python_sdk/site/reference/lib/io/models.html +241 -13
- dimine_python_sdk/site/reference/lib/native/algorithm.html +241 -13
- dimine_python_sdk/site/reference/lib/native/data_table.html +397 -87
- dimine_python_sdk/site/reference/lib/native/exploitation.html +241 -13
- dimine_python_sdk/site/reference/lib/native/geometry.html +424 -172
- dimine_python_sdk/site/reference/lib/native/io.html +241 -13
- dimine_python_sdk/site/reference/lib/native/models.html +241 -13
- dimine_python_sdk/site/reference/lib/native/prospecting.html +241 -13
- dimine_python_sdk/site/reference/lib/prospecting/api.html +321 -45
- dimine_python_sdk/site/reference/lib/prospecting/block_model.html +241 -13
- dimine_python_sdk/site/reference/lib/prospecting/compositing.html +275 -35
- dimine_python_sdk/site/reference/lib/prospecting/drill_db.html +241 -13
- dimine_python_sdk/site/reference/lib/prospecting/ellipsoid.html +241 -13
- dimine_python_sdk/site/reference/lib/prospecting/high_grade.html +308 -44
- dimine_python_sdk/site/reference/lib/prospecting/models.html +241 -13
- dimine_python_sdk/site/reference/lib/visualization/exceptions.html +241 -13
- dimine_python_sdk/site/reference/lib/visualization/models.html +241 -13
- dimine_python_sdk/site/reference/lib/visualization/tin_renderer.html +241 -13
- dimine_python_sdk/site/reference/logger.html +241 -13
- dimine_python_sdk/site/reference/models/conn.html +241 -13
- dimine_python_sdk/site/reference/models/errors.html +241 -13
- dimine_python_sdk/site/reference/models/types.html +349 -497
- dimine_python_sdk/site/search/search_index.json +1 -1
- dimine_python_sdk/site/sitemap.xml +108 -84
- dimine_python_sdk/site/sitemap.xml.gz +0 -0
- dimine_python_sdk/site/summary.html +241 -13
- {dimine_python_sdk-0.2.6.dist-info → dimine_python_sdk-0.2.8.dist-info}/METADATA +1 -1
- {dimine_python_sdk-0.2.6.dist-info → dimine_python_sdk-0.2.8.dist-info}/RECORD +145 -116
- {dimine_python_sdk-0.2.6.dist-info → dimine_python_sdk-0.2.8.dist-info}/WHEEL +0 -0
- {dimine_python_sdk-0.2.6.dist-info → dimine_python_sdk-0.2.8.dist-info}/entry_points.txt +0 -0
- {dimine_python_sdk-0.2.6.dist-info → dimine_python_sdk-0.2.8.dist-info}/top_level.txt +0 -0
dimine_python_sdk/__init__.py
CHANGED
|
@@ -4,7 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
6
|
DOCS_PATH = Path(__file__).parent / "docs"
|
|
7
|
-
__VERSION__ = "0.2.
|
|
7
|
+
__VERSION__ = "0.2.8" # 由 build.bat 中的 generate_version.py 自动更新
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
_LAZY_IMPORTS: dict[str, Any] = {}
|
|
@@ -36,19 +36,50 @@
|
|
|
36
36
|
dimine datatable-set --file PATH --index INT --field NAME --value VAL
|
|
37
37
|
dimine convert-file --source PATH --target PATH [--overwrite BOOL]
|
|
38
38
|
dimine laneway-tunnel --type INT --width FLOAT [--center-lines x,y,z;x,y,z;...] [--height FLOAT] [--width-up FLOAT] [--close] [--connectivity] [--method INT] [--no-import]
|
|
39
|
+
dimine start [--home PATH] [--wait SECONDS]
|
|
39
40
|
|
|
40
41
|
全局选项:
|
|
41
42
|
--format [json|text] 输出格式(默认: json)
|
|
42
43
|
"""
|
|
43
44
|
|
|
44
45
|
import argparse
|
|
46
|
+
import gettext
|
|
45
47
|
import sys
|
|
46
48
|
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
# 短路 gettext:argparse 在 ArgumentParser.__init__ 里固定做 3 次翻译
|
|
51
|
+
# (positional arguments / options / show this help message and exit),
|
|
52
|
+
# 每次 gettext.find() 都会遍历 locale 目录做 os.path.exists。本 CLI 会创建
|
|
53
|
+
# ~37 个 parser,合计 110 次翻译、约 440 次 stat,在慢盘/杀软环境下可占
|
|
54
|
+
# build_parser 总耗时的 90% 以上(实测 300ms+ → 8ms)。
|
|
55
|
+
#
|
|
56
|
+
# 本 CLI 没有随包分发 .mo 翻译目录,翻译结果恒等于原文;仅当确实存在
|
|
57
|
+
# messages.mo 时才回退到真实实现,保证将来若补翻译文件不会静默失效。
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
if gettext.find("messages") is None:
|
|
60
|
+
_real_find = gettext.find
|
|
61
|
+
_find_cache: dict = {}
|
|
62
|
+
|
|
63
|
+
def _cached_find(*args, **kwargs):
|
|
64
|
+
"""按参数缓存 gettext.find,避免重复探测文件系统。"""
|
|
65
|
+
key = (args, tuple(sorted(kwargs.items())))
|
|
66
|
+
if key not in _find_cache:
|
|
67
|
+
_find_cache[key] = _real_find(*args, **kwargs)
|
|
68
|
+
return _find_cache[key]
|
|
69
|
+
|
|
70
|
+
gettext.find = _cached_find
|
|
71
|
+
# argparse 模块级绑定了 `from gettext import gettext as _, ngettext`,
|
|
72
|
+
# 须直接替换其命名空间中的引用才生效。
|
|
73
|
+
argparse._ = lambda message: message
|
|
74
|
+
argparse.ngettext = lambda singular, plural, n: singular if n == 1 else plural
|
|
75
|
+
|
|
47
76
|
# Windows 控制台默认使用 GBK,强制改为 UTF-8 避免中文乱码
|
|
48
77
|
if sys.platform == "win32":
|
|
49
78
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
50
79
|
sys.stderr.reconfigure(encoding="utf-8")
|
|
51
80
|
|
|
81
|
+
from dimine_python_sdk.cli.commands.app import register_commands as register_app_commands
|
|
82
|
+
from dimine_python_sdk.cli.commands.app import COMMAND_HANDLERS as APP_HANDLERS
|
|
52
83
|
from dimine_python_sdk.cli.commands.camera import register_commands as register_camera_commands
|
|
53
84
|
from dimine_python_sdk.cli.commands.camera import COMMAND_HANDLERS as CAMERA_HANDLERS
|
|
54
85
|
from dimine_python_sdk.cli.commands.text import register_commands as register_text_commands
|
|
@@ -71,6 +102,7 @@ from dimine_python_sdk.cli.commands.layer import register_commands as register_l
|
|
|
71
102
|
from dimine_python_sdk.cli.commands.layer import COMMAND_HANDLERS as LAYER_HANDLERS
|
|
72
103
|
|
|
73
104
|
ALL_HANDLERS = {}
|
|
105
|
+
ALL_HANDLERS.update(APP_HANDLERS)
|
|
74
106
|
ALL_HANDLERS.update(ENTITY_HANDLERS)
|
|
75
107
|
ALL_HANDLERS.update(FILE_HANDLERS)
|
|
76
108
|
ALL_HANDLERS.update(LAYER_HANDLERS)
|
|
@@ -96,6 +128,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
96
128
|
sub = parser.add_subparsers(dest="command", help="子命令")
|
|
97
129
|
|
|
98
130
|
register_entity_commands(sub, format_parent)
|
|
131
|
+
register_app_commands(sub, format_parent)
|
|
99
132
|
register_file_commands(sub, format_parent)
|
|
100
133
|
register_layer_commands(sub, format_parent)
|
|
101
134
|
register_feature_commands(sub, format_parent)
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""Dimine 应用进程相关 CLI 命令(本地进程操作,无需 Dimine 已运行)。
|
|
2
|
+
|
|
3
|
+
本命令从 DIMINE_HOME 定位 Dimine.exe 并在后台分离启动,用于在执行
|
|
4
|
+
file-tree / file-open 等远程命令前先把 Dimine 桌面程序拉起来。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import csv
|
|
9
|
+
import io
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import subprocess
|
|
13
|
+
import time
|
|
14
|
+
|
|
15
|
+
from dimine_python_sdk.cli.utils._utils import output_error, output_result
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
_DIMINE_EXE = "Dimine.exe"
|
|
20
|
+
# 与 conn_client.open_client() 默认地址保持一致(Dimine 内置 WebSocket 服务)
|
|
21
|
+
_WS_URL = "ws://127.0.0.1:8000"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ==============================================================================
|
|
25
|
+
# 进程辅助函数
|
|
26
|
+
# ==============================================================================
|
|
27
|
+
|
|
28
|
+
def _running_pids() -> list:
|
|
29
|
+
"""返回当前正在运行的 Dimine.exe 进程 PID 列表(Windows)。"""
|
|
30
|
+
if os.name != "nt":
|
|
31
|
+
return []
|
|
32
|
+
try:
|
|
33
|
+
proc = subprocess.run(
|
|
34
|
+
["tasklist", "/FI", f"IMAGENAME eq {_DIMINE_EXE}", "/FO", "CSV", "/NH"],
|
|
35
|
+
capture_output=True,
|
|
36
|
+
timeout=10,
|
|
37
|
+
)
|
|
38
|
+
except (OSError, subprocess.SubprocessError):
|
|
39
|
+
return []
|
|
40
|
+
text = proc.stdout.decode("utf-8", errors="replace")
|
|
41
|
+
pids = []
|
|
42
|
+
for row in csv.reader(io.StringIO(text)):
|
|
43
|
+
if len(row) >= 2 and row[0].strip().lower() == _DIMINE_EXE.lower():
|
|
44
|
+
try:
|
|
45
|
+
pids.append(int(row[1].strip()))
|
|
46
|
+
except ValueError:
|
|
47
|
+
pass
|
|
48
|
+
return pids
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _launch(exe: str, home: str, uac_timeout: float | None = None) -> int | None:
|
|
52
|
+
"""后台分离启动 Dimine.exe,返回 pid(提权路径未探测到 pid 时为 None)。
|
|
53
|
+
|
|
54
|
+
以 DIMINE_HOME 为工作目录,保证相对路径的 SysParameter / Parameter 等
|
|
55
|
+
资源能被正确找到;子进程独立于当前控制台运行,CLI 立即返回。
|
|
56
|
+
|
|
57
|
+
若 Dimine.exe 要求管理员权限(Popen 抛 WinError 740)而当前进程未提权,
|
|
58
|
+
自动改用 runas 动词触发一次 UAC 提权启动(见 _launch_elevated)。
|
|
59
|
+
uac_timeout:仅提权路径使用——等待用户在 UAC 上应答的最长秒数,超时抛
|
|
60
|
+
RuntimeError;None 表示不限时(可 Ctrl+C 中止)。
|
|
61
|
+
"""
|
|
62
|
+
env = os.environ.copy()
|
|
63
|
+
env["DIMINE_HOME"] = home
|
|
64
|
+
kwargs = dict(
|
|
65
|
+
cwd=home,
|
|
66
|
+
env=env,
|
|
67
|
+
stdin=subprocess.DEVNULL,
|
|
68
|
+
stdout=subprocess.DEVNULL,
|
|
69
|
+
stderr=subprocess.DEVNULL,
|
|
70
|
+
)
|
|
71
|
+
if os.name == "nt":
|
|
72
|
+
kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS
|
|
73
|
+
try:
|
|
74
|
+
proc = subprocess.Popen([exe], **kwargs)
|
|
75
|
+
except OSError as exc:
|
|
76
|
+
# WinError 740 = ERROR_ELEVATION_REQUIRED:目标 exe manifest 要求管理员权限。
|
|
77
|
+
if os.name == "nt" and getattr(exc, "winerror", None) == 740:
|
|
78
|
+
return _launch_elevated(exe, home, uac_timeout)
|
|
79
|
+
raise
|
|
80
|
+
logger.info("已后台启动 Dimine(pid=%s exe=%s)", proc.pid, exe)
|
|
81
|
+
return proc.pid
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _launch_elevated(exe: str, home: str, timeout: float | None = None) -> int | None:
|
|
85
|
+
"""通过 runas 动词触发 UAC 提权启动 Dimine.exe,返回探测到的 pid 或 None。
|
|
86
|
+
|
|
87
|
+
提权进程由系统(consent.exe 经 UAC 确认)独立创建,调用方拿不到其句柄,
|
|
88
|
+
故启动后轮询 tasklist 直至 Dimine.exe 出现以取得 pid。
|
|
89
|
+
|
|
90
|
+
ShellExecuteW(runas) 会同步阻塞到用户在 UAC 上应答,若主线程直接调用会
|
|
91
|
+
卡死在 C 函数里(Ctrl+C 无效)。因此改在守护线程中执行、主线程空闲轮询:
|
|
92
|
+
- Ctrl+C 随时可中止(进程退出会顺带取消那次未决的提权);
|
|
93
|
+
- timeout>0 时,超过该秒数仍未获 UAC 应答则抛 RuntimeError;
|
|
94
|
+
- 用户点『否』/取消 UAC → RuntimeError(面向用户的中文提示)。
|
|
95
|
+
|
|
96
|
+
ShellExecute 子进程继承本进程环境变量,因此先把 DIMINE_HOME 临时写入
|
|
97
|
+
os.environ,确保目标进程能看到正确的资源目录。
|
|
98
|
+
"""
|
|
99
|
+
if os.name != "nt":
|
|
100
|
+
raise RuntimeError("提权启动仅支持 Windows。")
|
|
101
|
+
import ctypes
|
|
102
|
+
import threading
|
|
103
|
+
from ctypes import wintypes
|
|
104
|
+
|
|
105
|
+
logger.warning(
|
|
106
|
+
"Dimine.exe 需要管理员权限,弹出 UAC 确认框以提权启动…"
|
|
107
|
+
+ ("" if timeout is None else f"(请在 {timeout:g}s 内应答,超时自动中止)")
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
outcome: dict = {}
|
|
111
|
+
|
|
112
|
+
def _request() -> None:
|
|
113
|
+
"""守护线程内调用 ShellExecuteW(runas),结果写入 outcome。"""
|
|
114
|
+
had_home = "DIMINE_HOME" in os.environ
|
|
115
|
+
old_home = os.environ.get("DIMINE_HOME")
|
|
116
|
+
os.environ["DIMINE_HOME"] = home
|
|
117
|
+
try:
|
|
118
|
+
shell32 = ctypes.windll.shell32
|
|
119
|
+
shell_exec = shell32.ShellExecuteW
|
|
120
|
+
shell_exec.argtypes = [
|
|
121
|
+
wintypes.HWND,
|
|
122
|
+
wintypes.LPCWSTR, # lpOperation = "runas"
|
|
123
|
+
wintypes.LPCWSTR, # lpFile
|
|
124
|
+
wintypes.LPCWSTR, # lpParameters
|
|
125
|
+
wintypes.LPCWSTR, # lpDirectory
|
|
126
|
+
ctypes.c_int, # nShowCmd
|
|
127
|
+
]
|
|
128
|
+
shell_exec.restype = wintypes.HINSTANCE
|
|
129
|
+
# 第 6 参为工作目录(home),第 7 参 SW_SHOWNORMAL=1
|
|
130
|
+
outcome["ret"] = shell_exec(None, "runas", exe, "", home, 1)
|
|
131
|
+
except Exception as exc: # noqa: BLE001 线程内兜底,避免调用悬空
|
|
132
|
+
outcome["exc"] = exc
|
|
133
|
+
finally:
|
|
134
|
+
if had_home:
|
|
135
|
+
os.environ["DIMINE_HOME"] = old_home
|
|
136
|
+
else:
|
|
137
|
+
os.environ.pop("DIMINE_HOME", None)
|
|
138
|
+
|
|
139
|
+
threading.Thread(target=_request, name="dimine-uac", daemon=True).start()
|
|
140
|
+
|
|
141
|
+
# 主线程空闲等待:Ctrl+C 可正常中断;timeout 内未应答则放弃(退出会取消提权)
|
|
142
|
+
start = time.monotonic()
|
|
143
|
+
while "ret" not in outcome and "exc" not in outcome:
|
|
144
|
+
if timeout is not None and time.monotonic() - start >= timeout:
|
|
145
|
+
raise RuntimeError(
|
|
146
|
+
f"在 {timeout:g}s 内未获 UAC 应答,{os.path.basename(exe)} 未启动。"
|
|
147
|
+
f"请重新运行本命令,并在弹出的 UAC 窗口选择『是』;"
|
|
148
|
+
f"或改为以管理员身份运行。"
|
|
149
|
+
)
|
|
150
|
+
time.sleep(0.2)
|
|
151
|
+
if "exc" in outcome:
|
|
152
|
+
raise outcome["exc"]
|
|
153
|
+
|
|
154
|
+
# ShellExecute 成功时返回 >32;失败返回 <=32 的 SE_ERR_*,UAC 被用户
|
|
155
|
+
# 拒绝时可能返回 ERROR_CANCELLED(1223)(数值 >32,需单独识别)。
|
|
156
|
+
SE_ERR_ACCESSDENIED = 5
|
|
157
|
+
ERROR_CANCELLED = 1223
|
|
158
|
+
ret = outcome.get("ret")
|
|
159
|
+
ret = int(ret) if ret else 0
|
|
160
|
+
if ret <= 32 or ret in (SE_ERR_ACCESSDENIED, ERROR_CANCELLED):
|
|
161
|
+
raise RuntimeError(
|
|
162
|
+
f"启动 {os.path.basename(exe)} 需要管理员权限,但 UAC 提权被取消或启动失败"
|
|
163
|
+
f"(ShellExecute 返回 {ret})。请在弹出的 UAC 窗口选择『是』,"
|
|
164
|
+
f"或改为以管理员身份运行本命令。"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# 提权进程创建有一瞬延迟,轮询 tasklist 以取得新进程 pid
|
|
168
|
+
deadline = time.monotonic() + 10.0
|
|
169
|
+
while time.monotonic() < deadline:
|
|
170
|
+
pids = _running_pids()
|
|
171
|
+
if pids:
|
|
172
|
+
logger.info("已通过 UAC 提权启动 Dimine(pid=%s exe=%s)", pids[0], exe)
|
|
173
|
+
return pids[0]
|
|
174
|
+
time.sleep(0.2)
|
|
175
|
+
logger.info("已触发 UAC 提权启动 Dimine(exe=%s),暂未探测到 pid", exe)
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# ==============================================================================
|
|
180
|
+
# 远端就绪探测
|
|
181
|
+
# ==============================================================================
|
|
182
|
+
|
|
183
|
+
def _wait_ready(url: str, timeout: float) -> tuple:
|
|
184
|
+
"""轮询 WebSocket 服务直至可连接或超时。
|
|
185
|
+
|
|
186
|
+
返回 (ready, waited_seconds)。握手成功即认为 Dimine 远端服务已就绪,
|
|
187
|
+
之后 file-tree / file-open 等远程命令即可直接使用。
|
|
188
|
+
"""
|
|
189
|
+
start = time.monotonic()
|
|
190
|
+
|
|
191
|
+
async def _probe() -> bool:
|
|
192
|
+
import websockets
|
|
193
|
+
try:
|
|
194
|
+
ws = await asyncio.wait_for(
|
|
195
|
+
websockets.connect(url, close_timeout=1), timeout=2
|
|
196
|
+
)
|
|
197
|
+
except Exception:
|
|
198
|
+
return False
|
|
199
|
+
try:
|
|
200
|
+
await ws.close()
|
|
201
|
+
except Exception:
|
|
202
|
+
pass
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
async def _poll() -> bool:
|
|
206
|
+
while True:
|
|
207
|
+
if await _probe():
|
|
208
|
+
return True
|
|
209
|
+
if time.monotonic() - start >= timeout:
|
|
210
|
+
return False
|
|
211
|
+
await asyncio.sleep(0.5)
|
|
212
|
+
|
|
213
|
+
ready = asyncio.run(_poll())
|
|
214
|
+
return ready, round(time.monotonic() - start, 1)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# ==============================================================================
|
|
218
|
+
# 命令实现
|
|
219
|
+
# ==============================================================================
|
|
220
|
+
|
|
221
|
+
def cmd_start(fmt: str, home: str | None = None, wait: float = 8.0) -> None:
|
|
222
|
+
"""后台启动 Dimine(自动定位 DIMINE_HOME/Dimine.exe 并分离启动)。
|
|
223
|
+
|
|
224
|
+
- 已运行则跳过启动,仅报告当前实例,不结束任何旧进程。
|
|
225
|
+
- --wait N:整个启动阶段的总预算秒数(默认 8)——需要提权时等用户在 UAC
|
|
226
|
+
上应答、随后等远端 WebSocket 就绪,都计入这 N 秒。
|
|
227
|
+
- 提权场景下 N 秒内 UAC 一直未获应答视为启动失败,报错退出。
|
|
228
|
+
- --wait 0:不设预算也不等待就绪(启动后立即返回,旧行为)。
|
|
229
|
+
"""
|
|
230
|
+
dimine_home = (home or os.environ.get("DIMINE_HOME") or "").strip()
|
|
231
|
+
if not dimine_home:
|
|
232
|
+
output_error("未检测到 DIMINE_HOME。请设置 DIMINE_HOME 环境变量指向 Dimine 安装目录,或传入 --home。", fmt)
|
|
233
|
+
dimine_home = os.path.abspath(dimine_home)
|
|
234
|
+
exe = os.path.join(dimine_home, _DIMINE_EXE)
|
|
235
|
+
if not os.path.isfile(exe):
|
|
236
|
+
output_error(f"在 DIMINE_HOME 下未找到 {_DIMINE_EXE}: {exe}", fmt)
|
|
237
|
+
|
|
238
|
+
budget = wait if (wait and wait > 0) else None
|
|
239
|
+
t0 = time.monotonic()
|
|
240
|
+
|
|
241
|
+
pids = _running_pids()
|
|
242
|
+
started_new, launched_pid = False, None
|
|
243
|
+
if pids:
|
|
244
|
+
logger.info("Dimine 已在运行(pid=%s),跳过启动", pids)
|
|
245
|
+
else:
|
|
246
|
+
try:
|
|
247
|
+
launched_pid = _launch(exe, dimine_home, uac_timeout=budget)
|
|
248
|
+
except OSError as exc:
|
|
249
|
+
# 非提权类的启动失败(权限/路径/占用等),转为友好报错而非原始 traceback
|
|
250
|
+
logger.error("启动 Dimine 失败: %s", exc)
|
|
251
|
+
output_error(f"启动 Dimine 失败: {exc}", fmt)
|
|
252
|
+
except RuntimeError as exc:
|
|
253
|
+
# 提权被取消 / UAC 超时等,_launch_elevated 已翻译为面向用户的文案
|
|
254
|
+
logger.error("启动 Dimine 失败: %s", exc)
|
|
255
|
+
output_error(str(exc), fmt)
|
|
256
|
+
started_new = True
|
|
257
|
+
pids = _running_pids() or ([launched_pid] if launched_pid else [])
|
|
258
|
+
|
|
259
|
+
remote_ready, waited = None, None
|
|
260
|
+
ready_budget = None
|
|
261
|
+
if budget is not None:
|
|
262
|
+
# 新启动的实例把启动耗时(含等待 UAC)计入预算;已运行的实例从新预算起算
|
|
263
|
+
ready_budget = wait if not started_new else max(0.0, wait - (time.monotonic() - t0))
|
|
264
|
+
if ready_budget > 0:
|
|
265
|
+
remote_ready, waited = _wait_ready(_WS_URL, ready_budget)
|
|
266
|
+
|
|
267
|
+
result = {
|
|
268
|
+
"success": True,
|
|
269
|
+
"started_new": started_new,
|
|
270
|
+
"pid": launched_pid if started_new else (pids[0] if pids else None),
|
|
271
|
+
"pids": pids,
|
|
272
|
+
"dimine_home": dimine_home,
|
|
273
|
+
"exe": exe,
|
|
274
|
+
}
|
|
275
|
+
if budget is not None:
|
|
276
|
+
result["wait_seconds"] = wait
|
|
277
|
+
if remote_ready is not None:
|
|
278
|
+
result["remote_ready"] = bool(remote_ready)
|
|
279
|
+
result["waited_seconds"] = waited
|
|
280
|
+
|
|
281
|
+
if started_new:
|
|
282
|
+
if launched_pid:
|
|
283
|
+
msg = f"已后台启动 Dimine: {exe}(pid={launched_pid})"
|
|
284
|
+
else:
|
|
285
|
+
msg = f"已触发 UAC 提权启动 Dimine: {exe}(新进程 pid 暂未探测到)"
|
|
286
|
+
else:
|
|
287
|
+
msg = f"Dimine 已在运行(pid={pids}),跳过启动;本命令不会结束旧实例,如需重启请先在 Dimine 中退出"
|
|
288
|
+
if remote_ready is True:
|
|
289
|
+
msg += f",远端服务 {_WS_URL} 已在 {waited}s 内就绪"
|
|
290
|
+
elif remote_ready is False:
|
|
291
|
+
msg += f",远端服务在 {ready_budget:g}s 内未就绪,可能仍在启动或启动失败,可加大 --wait 重试"
|
|
292
|
+
result["message"] = msg
|
|
293
|
+
|
|
294
|
+
output_result(result, fmt)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def register_commands(subparsers, format_parent) -> None:
|
|
298
|
+
"""向 subparsers 注册 Dimine 应用进程相关子命令。"""
|
|
299
|
+
p = subparsers.add_parser(
|
|
300
|
+
"start",
|
|
301
|
+
parents=[format_parent],
|
|
302
|
+
help="后台启动 Dimine(自动定位 DIMINE_HOME/Dimine.exe)",
|
|
303
|
+
)
|
|
304
|
+
p.add_argument("--home", default=None, help="Dimine 安装目录(默认取 DIMINE_HOME 环境变量)")
|
|
305
|
+
p.add_argument(
|
|
306
|
+
"--wait",
|
|
307
|
+
type=float,
|
|
308
|
+
default=8.0,
|
|
309
|
+
help=(
|
|
310
|
+
"启动总预算秒数:提权启动时等 UAC 应答 + 之后等远端 WebSocket 就绪,"
|
|
311
|
+
"合计超过则报错/返回(默认 8;0=不等待就绪且不设预算)"
|
|
312
|
+
),
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
COMMAND_HANDLERS = {
|
|
317
|
+
"start": lambda args: cmd_start(args.format, args.home, args.wait),
|
|
318
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"""文件相关 CLI 命令。"""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
|
|
3
6
|
from dimine_python_sdk.cli.utils._utils import (
|
|
4
7
|
get_client,
|
|
5
8
|
handle_sdk_error,
|
|
@@ -11,6 +14,8 @@ from dimine_python_sdk.cli.utils._utils import (
|
|
|
11
14
|
run_cli_async,
|
|
12
15
|
)
|
|
13
16
|
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
14
19
|
|
|
15
20
|
def cmd_file_tree(fmt: str) -> None:
|
|
16
21
|
"""获取当前场景中已打开的所有文件及每个文件的图层信息(需 Dimine 运行)。"""
|
|
@@ -81,7 +86,7 @@ def cmd_file_save(fmt: str, output_path: str, file_id: str | None) -> None:
|
|
|
81
86
|
|
|
82
87
|
|
|
83
88
|
def cmd_file_open(fmt: str, path: str) -> None:
|
|
84
|
-
"""在 Dimine 中打开文件(需 Dimine
|
|
89
|
+
"""在 Dimine 中打开文件(需 Dimine 运行)。同名文件已打开则先关闭再重开;打开 .dmo 前会把已打开的其它 .dmo 一并关闭。"""
|
|
85
90
|
err = require_remote_sdk()
|
|
86
91
|
if err:
|
|
87
92
|
output_error(err, fmt)
|
|
@@ -91,13 +96,28 @@ def cmd_file_open(fmt: str, path: str) -> None:
|
|
|
91
96
|
async def _run():
|
|
92
97
|
client = await get_client()
|
|
93
98
|
|
|
99
|
+
# 先确认待打开的新文件存在,再考虑关闭已打开文件,避免误关旧文件后才发现打不开
|
|
100
|
+
if not os.path.isfile(file_path):
|
|
101
|
+
raise_or_exit(f"文件不存在: {file_path}", fmt)
|
|
102
|
+
|
|
103
|
+
# Dimine 同一时刻只能打开一个 .dmo(炮孔数据库)文件:打开 .dmo 前若已有
|
|
104
|
+
# .dmo 打开须先关闭,否则 open_file 会提示 "file already opened"。
|
|
105
|
+
is_dmo = str(file_path).lower().endswith(".dmo")
|
|
94
106
|
# 检查是否有同名文件已打开,如果有则先关闭
|
|
95
107
|
target_name = path
|
|
96
108
|
opened_files = await client.get_files()
|
|
97
109
|
for f in opened_files:
|
|
98
|
-
if
|
|
99
|
-
|
|
100
|
-
|
|
110
|
+
if is_dmo:
|
|
111
|
+
# DMO:关闭当前已打开的所有 .dmo 文件
|
|
112
|
+
if (f.name or "").lower().endswith(".dmo"):
|
|
113
|
+
logger.info("file-open: 打开新 .dmo 前关闭已打开文件(id=%s name=%s)", f.id, f.name)
|
|
114
|
+
await client.close_file(f.id)
|
|
115
|
+
else:
|
|
116
|
+
# 非 DMO:仅当同名文件已打开时先关闭再重新打开
|
|
117
|
+
if f.name == target_name:
|
|
118
|
+
logger.info("file-open: 同名文件已打开,先关闭再重开(id=%s name=%s)", f.id, f.name)
|
|
119
|
+
await client.close_file(f.id)
|
|
120
|
+
break
|
|
101
121
|
|
|
102
122
|
# 打开文件
|
|
103
123
|
await client.open_file(file_path)
|
|
@@ -70,8 +70,8 @@ def cmd_layer_create(fmt: str, name: str, file_id: str | None) -> None:
|
|
|
70
70
|
handle_sdk_error(e, fmt, "创建图层失败")
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
def cmd_layer_visibility(fmt: str, file_id: str,
|
|
74
|
-
"""
|
|
73
|
+
def cmd_layer_visibility(fmt: str, file_id: str, layer_names: list[str], action: str) -> None:
|
|
74
|
+
"""设置图层显示/隐藏状态,支持一次操作多个图层(需 Dimine 运行)。"""
|
|
75
75
|
err = require_remote_sdk()
|
|
76
76
|
if err:
|
|
77
77
|
output_error(err, fmt)
|
|
@@ -86,15 +86,15 @@ def cmd_layer_visibility(fmt: str, file_id: str, layer_name: str, action: str) -
|
|
|
86
86
|
|
|
87
87
|
async def _run():
|
|
88
88
|
client = await get_client()
|
|
89
|
-
await client.
|
|
90
|
-
|
|
91
|
-
layer_name=layer_name,
|
|
89
|
+
await client.set_layers_visibility(
|
|
90
|
+
layers=[{"file": file_id, "layer": name} for name in layer_names],
|
|
92
91
|
action=valid_actions[action],
|
|
93
92
|
)
|
|
94
93
|
action_cn = {"show": "显示", "hide": "隐藏", "exclusive_show": "排他显示"}
|
|
95
94
|
return {
|
|
96
95
|
"success": True,
|
|
97
|
-
"
|
|
96
|
+
"layer_count": len(layer_names),
|
|
97
|
+
"message": f"已成功{action_cn.get(action, action)}图层 {'、'.join(layer_names)}",
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
try:
|
|
@@ -150,7 +150,8 @@ def register_commands(subparsers, format_parent) -> None:
|
|
|
150
150
|
|
|
151
151
|
p = subparsers.add_parser("layer-visibility", parents=[format_parent], help="设置图层显示/隐藏状态")
|
|
152
152
|
p.add_argument("--file-id", required=True, help="文件ID")
|
|
153
|
-
p.add_argument("--layer-name", required=True,
|
|
153
|
+
p.add_argument("--layer-name", action="append", required=True, dest="layer_names",
|
|
154
|
+
help="图层名称,可重复指定以批量操作")
|
|
154
155
|
p.add_argument("--action", choices=["show", "hide", "exclusive_show"], default="show", help="操作类型(默认: show)")
|
|
155
156
|
|
|
156
157
|
|
|
@@ -158,5 +159,5 @@ COMMAND_HANDLERS = {
|
|
|
158
159
|
"layers": lambda args: cmd_layers(args.format, args.file_id),
|
|
159
160
|
"layer-create": lambda args: cmd_layer_create(args.format, args.name, args.file_id),
|
|
160
161
|
"layer-delete": lambda args: cmd_layer_delete(args.format, args.name, args.file_id),
|
|
161
|
-
"layer-visibility": lambda args: cmd_layer_visibility(args.format, args.file_id, args.
|
|
162
|
+
"layer-visibility": lambda args: cmd_layer_visibility(args.format, args.file_id, args.layer_names, args.action),
|
|
162
163
|
}
|
|
@@ -14,6 +14,7 @@ from dimine_python_sdk.logger import logger
|
|
|
14
14
|
from dimine_python_sdk.models.conn import ConnEvent, ConnRequest, ConnResponse,CommandType
|
|
15
15
|
from dimine_python_sdk.models.errors import ResponseError
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
@dataclass
|
|
18
19
|
class BaseConnClient:
|
|
19
20
|
model_config = {"arbitrary_types_allowed": True}
|
|
@@ -137,8 +138,7 @@ class BaseConnClient:
|
|
|
137
138
|
self.pending_requests.pop(request.request_id)
|
|
138
139
|
raise e
|
|
139
140
|
else:
|
|
140
|
-
|
|
141
|
-
return result
|
|
141
|
+
return await future
|
|
142
142
|
|
|
143
143
|
async def _send_command(self, command: CommandType, payload:Optional[Any]=None, callback: Optional[Callable] = None)->ConnResponse:
|
|
144
144
|
"""发送请求并等待响应(内部方法,外部不应直接调用)"""
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
2
4
|
from contextlib import asynccontextmanager
|
|
3
5
|
from pathlib import Path
|
|
4
6
|
import shutil
|
|
5
7
|
import tempfile
|
|
6
8
|
from typing import Any, AsyncGenerator, Dict, List, Literal, Optional
|
|
7
|
-
import numpy as np
|
|
8
9
|
from dimine_python_sdk.conn.base_conn_client import BaseConnClient
|
|
9
10
|
from dimine_python_sdk.models import FileInfo, ResponseStatus, CommandType,LayerInfo, FeatureInfo, SelectedItem, SelectedResult, SelectMode, VisibilityAction
|
|
10
11
|
|
|
11
|
-
from dimine_python_sdk.models.types import
|
|
12
|
+
from dimine_python_sdk.models.types import TextInfo
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class ConnClient(BaseConnClient):
|
|
@@ -138,6 +139,12 @@ class ConnClient(BaseConnClient):
|
|
|
138
139
|
threshold: int,
|
|
139
140
|
) -> bool:
|
|
140
141
|
"""判断单个几何体是否应走本地文件传输路径。"""
|
|
142
|
+
# 几何类型与 numpy 均为惰性导入:远程命令路径(file-tree/layers 等)不碰它们,
|
|
143
|
+
# 从而避免在导入期拉起 numpy。
|
|
144
|
+
import numpy as np
|
|
145
|
+
|
|
146
|
+
from dimine_python_sdk.models.geometry import Line, Point, Shell
|
|
147
|
+
|
|
141
148
|
if file_transfer is True:
|
|
142
149
|
return True
|
|
143
150
|
if file_transfer is False:
|
|
@@ -181,6 +188,10 @@ class ConnClient(BaseConnClient):
|
|
|
181
188
|
|
|
182
189
|
def _entity_type_for_file_transfer(self, geom: GEOMETRY) -> str:
|
|
183
190
|
"""为文件传输路径计算与 DMF 实体一致的 entity_type。"""
|
|
191
|
+
import numpy as np
|
|
192
|
+
|
|
193
|
+
from dimine_python_sdk.models.geometry import Line, Point, Shell
|
|
194
|
+
|
|
184
195
|
if isinstance(geom, Point):
|
|
185
196
|
return "point"
|
|
186
197
|
if isinstance(geom, Line):
|
|
@@ -192,6 +203,10 @@ class ConnClient(BaseConnClient):
|
|
|
192
203
|
|
|
193
204
|
def _insert_geometry_into_dmf(self, dmf, geom: GEOMETRY) -> None:
|
|
194
205
|
"""将几何体写入 DMF 内存模型(图层名为 ``"0"``)。"""
|
|
206
|
+
import numpy as np
|
|
207
|
+
|
|
208
|
+
from dimine_python_sdk.models.geometry import Line, Point, Shell
|
|
209
|
+
|
|
195
210
|
name = geom.feature or "0"
|
|
196
211
|
if isinstance(geom, Point):
|
|
197
212
|
dmf.insert_point("0", geom.geometry, name=name)
|
|
@@ -264,6 +279,10 @@ class ConnClient(BaseConnClient):
|
|
|
264
279
|
features: 获取哪些要素相关的模型
|
|
265
280
|
with_geometry: 是否返回模型坐标信息,默认 False
|
|
266
281
|
"""
|
|
282
|
+
import numpy as np
|
|
283
|
+
|
|
284
|
+
from dimine_python_sdk.models.geometry import Line, Point, Shell
|
|
285
|
+
|
|
267
286
|
# 先获取要素信息,
|
|
268
287
|
all_features = await self.get_features(file_id)
|
|
269
288
|
|
|
@@ -409,6 +428,8 @@ class ConnClient(BaseConnClient):
|
|
|
409
428
|
file_transfer_threshold: int = 10000,
|
|
410
429
|
) -> None:
|
|
411
430
|
"""创建或更新多个模型, 如果指定了file_id和layer_id, 则将模型添加到指定的文件及图层中"""
|
|
431
|
+
from dimine_python_sdk.models.geometry import Line, Point, Shell
|
|
432
|
+
|
|
412
433
|
if isinstance(geometries, (Point, Line, Shell)):
|
|
413
434
|
geometries = [geometries]
|
|
414
435
|
update_geometries = [g for g in geometries if g.related]
|
|
@@ -604,27 +625,45 @@ class ConnClient(BaseConnClient):
|
|
|
604
625
|
if response.status == ResponseStatus.FAIL:
|
|
605
626
|
raise Exception(response.error)
|
|
606
627
|
|
|
607
|
-
async def
|
|
608
|
-
|
|
609
|
-
"""
|
|
628
|
+
async def set_layers_visibility(self, layers: List[Dict[str, str]],
|
|
629
|
+
action: VisibilityAction | str = VisibilityAction.SHOW) -> None:
|
|
630
|
+
"""批量设置图层显示/隐藏状态
|
|
610
631
|
|
|
611
632
|
Args:
|
|
612
|
-
|
|
613
|
-
|
|
633
|
+
layers: 要操作的图层列表,每个元素包含:
|
|
634
|
+
- file: 文件ID或文件名称
|
|
635
|
+
- layer: 图层名称
|
|
614
636
|
action: 操作类型,可选 show(显示) | hide(隐藏) | exclusive_show(排他性显示)
|
|
615
637
|
|
|
616
638
|
Raises:
|
|
617
639
|
Exception: 操作失败时抛出异常
|
|
618
640
|
"""
|
|
641
|
+
if not layers:
|
|
642
|
+
return
|
|
619
643
|
payload = {
|
|
620
|
-
"
|
|
621
|
-
"layer": layer_name,
|
|
644
|
+
"layers": layers,
|
|
622
645
|
"action": str(action)
|
|
623
646
|
}
|
|
624
647
|
response = await self._send_command(CommandType.LAYER_VISIBILITY, payload)
|
|
625
648
|
if response.status == ResponseStatus.FAIL:
|
|
626
649
|
raise Exception(response.error)
|
|
627
650
|
|
|
651
|
+
async def set_layer_visibility(self, file_id: str, layer_name: str,
|
|
652
|
+
action: VisibilityAction | str = VisibilityAction.SHOW) -> None:
|
|
653
|
+
"""设置单个图层的显示/隐藏状态
|
|
654
|
+
|
|
655
|
+
Args:
|
|
656
|
+
file_id: 文件ID或文件名称
|
|
657
|
+
layer_name: 图层名称
|
|
658
|
+
action: 操作类型,可选 show(显示) | hide(隐藏) | exclusive_show(排他性显示)
|
|
659
|
+
|
|
660
|
+
Raises:
|
|
661
|
+
Exception: 操作失败时抛出异常
|
|
662
|
+
"""
|
|
663
|
+
await self.set_layers_visibility(
|
|
664
|
+
[{"file": file_id, "layer": layer_name}], action=action
|
|
665
|
+
)
|
|
666
|
+
|
|
628
667
|
async def set_file_visibility(self, file_id: str,
|
|
629
668
|
action: VisibilityAction | str = VisibilityAction.SHOW) -> None:
|
|
630
669
|
"""设置文件显示/隐藏状态
|
|
@@ -176,6 +176,7 @@ $ dimine geometry-create --geometries '[{"type":"line","file":"f-001","layer":"l
|
|
|
176
176
|
$ dimine layer-visibility --file-id f-20240527-001 --layer-name 矿体 --action exclusive_show
|
|
177
177
|
{
|
|
178
178
|
"success": true,
|
|
179
|
+
"layer_count": 1,
|
|
179
180
|
"message": "已成功排他显示图层 矿体"
|
|
180
181
|
}
|
|
181
182
|
```
|
|
@@ -186,11 +187,25 @@ $ dimine layer-visibility --file-id f-20240527-001 --layer-name 矿体 --action
|
|
|
186
187
|
$ dimine layer-visibility --file-id f-20240527-001 --layer-name 巷道 --action hide
|
|
187
188
|
{
|
|
188
189
|
"success": true,
|
|
190
|
+
"layer_count": 1,
|
|
189
191
|
"message": "已成功隐藏图层 巷道"
|
|
190
192
|
}
|
|
191
193
|
```
|
|
192
194
|
|
|
193
|
-
### 3.
|
|
195
|
+
### 3. 批量操作同一文件下的多个图层
|
|
196
|
+
|
|
197
|
+
`--layer-name` 可重复指定,一次批量操作多个图层。
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
$ dimine layer-visibility --file-id f-20240527-001 --layer-name 矿体 --layer-name 巷道 --action exclusive_show
|
|
201
|
+
{
|
|
202
|
+
"success": true,
|
|
203
|
+
"layer_count": 2,
|
|
204
|
+
"message": "已成功排他显示图层 矿体、巷道"
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 4. 隐藏整个文件的所有图层
|
|
194
209
|
|
|
195
210
|
```bash
|
|
196
211
|
$ dimine file-visibility --file-id f-20240527-001 --action hide
|