dimine-python-sdk 0.2.2__py3-none-any.whl → 0.2.4__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 (152) hide show
  1. dimine_python_sdk/__init__.py +24 -4
  2. dimine_python_sdk/cli/commands/camera.py +3 -4
  3. dimine_python_sdk/cli/commands/entity.py +5 -6
  4. dimine_python_sdk/cli/commands/feature.py +5 -6
  5. dimine_python_sdk/cli/commands/file.py +31 -24
  6. dimine_python_sdk/cli/commands/geometry.py +9 -9
  7. dimine_python_sdk/cli/commands/laneway.py +3 -3
  8. dimine_python_sdk/cli/commands/layer.py +5 -6
  9. dimine_python_sdk/cli/commands/text.py +4 -4
  10. dimine_python_sdk/cli/utils/_utils.py +70 -28
  11. dimine_python_sdk/conn/base_conn_client.py +5 -1
  12. dimine_python_sdk/conn/conn_client.py +2 -2
  13. dimine_python_sdk/docs/guides/lib/io/SUMMARY.md +1 -0
  14. dimine_python_sdk/docs/guides/lib/io/dmo_file.md +159 -0
  15. dimine_python_sdk/docs/reference/SUMMARY.md +1 -0
  16. dimine_python_sdk/docs/reference/lib/io/dmo_file.md +3 -0
  17. dimine_python_sdk/lib/__init__.py +19 -4
  18. dimine_python_sdk/lib/io/__init__.py +19 -0
  19. dimine_python_sdk/lib/io/dmo_file.py +327 -0
  20. dimine_python_sdk/lib/io/file_converter.py +3 -3
  21. dimine_python_sdk/lib/native/__init__.py +4 -0
  22. dimine_python_sdk/lib/native/io.py +22 -10
  23. dimine_python_sdk/models/types.py +9 -9
  24. dimine_python_sdk/site/404.html +28 -0
  25. dimine_python_sdk/site/examples/cli_usage.html +28 -0
  26. dimine_python_sdk/site/examples/contour_modeling_visualization.html +28 -0
  27. dimine_python_sdk/site/examples/import_csv_to_drill_db.html +28 -0
  28. dimine_python_sdk/site/examples/lib_file_usage.html +28 -0
  29. dimine_python_sdk/site/examples/reserve_estimation_pipeline.html +28 -0
  30. dimine_python_sdk/site/examples/summary.html +28 -0
  31. dimine_python_sdk/site/guides/SUMMARY.html +28 -0
  32. dimine_python_sdk/site/guides/cli.html +28 -0
  33. dimine_python_sdk/site/guides/conn/SUMMARY.html +28 -0
  34. dimine_python_sdk/site/guides/conn/data_edit.html +28 -0
  35. dimine_python_sdk/site/guides/conn/data_get.html +28 -0
  36. dimine_python_sdk/site/guides/conn/features.html +28 -0
  37. dimine_python_sdk/site/guides/conn/io.html +28 -0
  38. dimine_python_sdk/site/guides/lib/SUMMARY.html +28 -0
  39. dimine_python_sdk/site/guides/lib/exploitation/SUMMARY.html +28 -0
  40. dimine_python_sdk/site/guides/lib/exploitation/contour_modeling.html +28 -0
  41. dimine_python_sdk/site/guides/lib/exploitation/implicit_modeling.html +28 -0
  42. dimine_python_sdk/site/guides/lib/exploitation/laneway.html +28 -0
  43. dimine_python_sdk/site/guides/lib/io/SUMMARY.html +29 -0
  44. dimine_python_sdk/site/guides/lib/io/dmb_file.html +28 -0
  45. dimine_python_sdk/site/guides/lib/io/dmd_file.html +28 -0
  46. dimine_python_sdk/site/guides/lib/io/dmf_file.html +28 -0
  47. dimine_python_sdk/site/guides/lib/io/dmg_file.html +29 -1
  48. dimine_python_sdk/site/guides/lib/io/dmo_file.html +2335 -0
  49. dimine_python_sdk/site/guides/lib/io/dmt_file.html +29 -1
  50. dimine_python_sdk/site/guides/lib/io/file_conversion.html +28 -0
  51. dimine_python_sdk/site/guides/lib/prospecting/SUMMARY.html +28 -0
  52. dimine_python_sdk/site/guides/lib/prospecting/drill_db.html +28 -0
  53. dimine_python_sdk/site/guides/lib/prospecting/reserve_estimation.html +28 -0
  54. dimine_python_sdk/site/guides/lib/visualization/SUMMARY.html +28 -0
  55. dimine_python_sdk/site/guides/lib/visualization/visualization.html +28 -0
  56. dimine_python_sdk/site/guides/models/SUMMARY.html +28 -0
  57. dimine_python_sdk/site/guides/models/data_types.html +28 -0
  58. dimine_python_sdk/site/index.html +28 -0
  59. dimine_python_sdk/site/objects.inv +0 -0
  60. dimine_python_sdk/site/quickstart.html +28 -0
  61. dimine_python_sdk/site/reference/SUMMARY.html +29 -0
  62. dimine_python_sdk/site/reference/cli/commands/camera.html +39 -11
  63. dimine_python_sdk/site/reference/cli/commands/convert.html +28 -0
  64. dimine_python_sdk/site/reference/cli/commands/datatable.html +28 -0
  65. dimine_python_sdk/site/reference/cli/commands/entity.html +46 -18
  66. dimine_python_sdk/site/reference/cli/commands/feature.html +47 -19
  67. dimine_python_sdk/site/reference/cli/commands/file.html +115 -69
  68. dimine_python_sdk/site/reference/cli/commands/geometry.html +39 -11
  69. dimine_python_sdk/site/reference/cli/commands/laneway.html +30 -2
  70. dimine_python_sdk/site/reference/cli/commands/layer.html +47 -19
  71. dimine_python_sdk/site/reference/cli/commands/text.html +34 -2
  72. dimine_python_sdk/site/reference/conn/base_conn_client.html +50 -14
  73. dimine_python_sdk/site/reference/conn/conn_client.html +28 -0
  74. dimine_python_sdk/site/reference/lib/bridge.html +132 -82
  75. dimine_python_sdk/site/reference/lib/exploitation/api.html +28 -0
  76. dimine_python_sdk/site/reference/lib/exploitation/contour_modeling.html +28 -0
  77. dimine_python_sdk/site/reference/lib/exploitation/implicit_modeling.html +28 -0
  78. dimine_python_sdk/site/reference/lib/exploitation/lane_way.html +28 -0
  79. dimine_python_sdk/site/reference/lib/exploitation/models.html +28 -0
  80. dimine_python_sdk/site/reference/lib/io/dmb_file.html +28 -0
  81. dimine_python_sdk/site/reference/lib/io/dmd_file.html +28 -0
  82. dimine_python_sdk/site/reference/lib/io/dmf_file.html +28 -0
  83. dimine_python_sdk/site/reference/lib/io/dmg_file.html +28 -0
  84. dimine_python_sdk/site/reference/lib/io/dmo_file.html +3003 -0
  85. dimine_python_sdk/site/reference/lib/io/dmt_file.html +28 -0
  86. dimine_python_sdk/site/reference/lib/io/file_converter.html +32 -4
  87. dimine_python_sdk/site/reference/lib/io/models.html +28 -0
  88. dimine_python_sdk/site/reference/lib/native/algorithm.html +28 -0
  89. dimine_python_sdk/site/reference/lib/native/data_table.html +28 -0
  90. dimine_python_sdk/site/reference/lib/native/exploitation.html +28 -0
  91. dimine_python_sdk/site/reference/lib/native/geometry.html +28 -0
  92. dimine_python_sdk/site/reference/lib/native/io.html +132 -82
  93. dimine_python_sdk/site/reference/lib/native/models.html +28 -0
  94. dimine_python_sdk/site/reference/lib/native/prospecting.html +28 -0
  95. dimine_python_sdk/site/reference/lib/prospecting/api.html +28 -0
  96. dimine_python_sdk/site/reference/lib/prospecting/block_model.html +28 -0
  97. dimine_python_sdk/site/reference/lib/prospecting/compositing.html +28 -0
  98. dimine_python_sdk/site/reference/lib/prospecting/drill_db.html +28 -0
  99. dimine_python_sdk/site/reference/lib/prospecting/ellipsoid.html +28 -0
  100. dimine_python_sdk/site/reference/lib/prospecting/high_grade.html +28 -0
  101. dimine_python_sdk/site/reference/lib/prospecting/models.html +28 -0
  102. dimine_python_sdk/site/reference/lib/visualization/exceptions.html +28 -0
  103. dimine_python_sdk/site/reference/lib/visualization/models.html +28 -0
  104. dimine_python_sdk/site/reference/lib/visualization/tin_renderer.html +28 -0
  105. dimine_python_sdk/site/reference/logger.html +28 -0
  106. dimine_python_sdk/site/reference/models/conn.html +28 -0
  107. dimine_python_sdk/site/reference/models/errors.html +28 -0
  108. dimine_python_sdk/site/reference/models/types.html +36 -8
  109. dimine_python_sdk/site/search/search_index.json +1 -1
  110. dimine_python_sdk/site/sitemap.xml +90 -82
  111. dimine_python_sdk/site/sitemap.xml.gz +0 -0
  112. dimine_python_sdk/site/summary.html +28 -0
  113. {dimine_python_sdk-0.2.2.dist-info → dimine_python_sdk-0.2.4.dist-info}/METADATA +1 -1
  114. {dimine_python_sdk-0.2.2.dist-info → dimine_python_sdk-0.2.4.dist-info}/RECORD +117 -147
  115. {dimine_python_sdk-0.2.2.dist-info → dimine_python_sdk-0.2.4.dist-info}/top_level.txt +0 -2
  116. outline/build_dmd_from_scratch.py +0 -426
  117. outline/contour_extract.py +0 -450
  118. outline/contour_match.py +0 -1095
  119. outline/contour_pipeline.py +0 -586
  120. outline/contour_validate.py +0 -645
  121. outline/dmd_to_dmg.py +0 -164
  122. outline/drill_to_reserves.py +0 -825
  123. outline/ellipsoid_params.py +0 -106
  124. outline/implicit_modeling.py +0 -365
  125. outline/read_dmb.py +0 -200
  126. outline/read_dwg.py +0 -353
  127. outline/test_dmd_file.py +0 -322
  128. outline/test_dmg_to_segments.py +0 -317
  129. outline/test_extend_table.py +0 -193
  130. tests/conn_example.py +0 -70
  131. tests/conn_test.py +0 -269
  132. tests/test_conn_client_unit.py +0 -449
  133. tests/test_create_geometry_file_transfer.py +0 -289
  134. tests/test_csv_import_flow.py +0 -169
  135. tests/test_dmg_file.py +0 -467
  136. tests/test_dmt_file.py +0 -178
  137. tests/test_file_conversion.py +0 -544
  138. tests/test_high_grade_samples.py +0 -228
  139. tests/test_implicit_modeling.py +0 -284
  140. tests/test_laneway.py +0 -341
  141. tests/test_layer_isolation.py +0 -219
  142. tests/test_lib_file.py +0 -731
  143. tests/test_lib_refactor.py +0 -304
  144. tests/test_modeling.py +0 -338
  145. tests/test_native_unit.py +0 -353
  146. tests/test_prospecting_api.py +0 -1287
  147. tests/test_sample_compositing.py +0 -398
  148. tests/test_tin_renderer.py +0 -454
  149. tests/ws_client.py +0 -60
  150. tests/ws_server.py +0 -271
  151. {dimine_python_sdk-0.2.2.dist-info → dimine_python_sdk-0.2.4.dist-info}/WHEEL +0 -0
  152. {dimine_python_sdk-0.2.2.dist-info → dimine_python_sdk-0.2.4.dist-info}/entry_points.txt +0 -0
@@ -1,13 +1,33 @@
1
- from pathlib import Path
1
+ from __future__ import annotations
2
2
 
3
- from .conn import ConnClient
3
+ from pathlib import Path
4
+ from typing import Any
4
5
 
5
6
  DOCS_PATH = Path(__file__).parent / "docs"
6
- __VERSION__ = "0.2.2" # 由 build.bat 中的 generate_version.py 自动更新
7
+ __VERSION__ = "0.2.4" # 由 build.bat 中的 generate_version.py 自动更新
8
+
9
+
10
+ _LAZY_IMPORTS: dict[str, Any] = {}
11
+
12
+
13
+ def __getattr__(name: str) -> Any:
14
+ """PEP 562: 惰性加载 ConnClient,避免导入期触发 numpy/pydantic/websockets 等重依赖。"""
15
+ if name == "ConnClient":
16
+ if name not in _LAZY_IMPORTS:
17
+ from dimine_python_sdk.conn import ConnClient as _ConnClient
18
+
19
+ _LAZY_IMPORTS[name] = _ConnClient
20
+ return _LAZY_IMPORTS[name]
21
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
22
+
23
+
24
+ def __dir__() -> list[str]:
25
+ """确保 dir() 和 IDE 自动补全能看到惰性加载的属性。"""
26
+ return sorted(set(list(globals()) + __all__ + list(_LAZY_IMPORTS)))
7
27
 
8
28
 
9
29
  __all__ = [
10
30
  "ConnClient",
11
31
  "DOCS_PATH",
12
- "__VERSION__"
32
+ "__VERSION__",
13
33
  ]
@@ -1,7 +1,5 @@
1
1
  """相机与截图相关 CLI 命令。"""
2
2
 
3
- import asyncio
4
-
5
3
  from dimine_python_sdk.cli.utils._utils import (
6
4
  get_client,
7
5
  handle_sdk_error,
@@ -9,6 +7,7 @@ from dimine_python_sdk.cli.utils._utils import (
9
7
  output_result,
10
8
  parse_json_arg,
11
9
  require_remote_sdk,
10
+ run_cli_async,
12
11
  )
13
12
 
14
13
 
@@ -35,7 +34,7 @@ def cmd_camera_set(fmt: str, center_point: str, normal_vector: str) -> None:
35
34
  }
36
35
 
37
36
  try:
38
- result = asyncio.run(_run())
37
+ result = run_cli_async(_run())
39
38
  output_result(result, fmt)
40
39
  except Exception as e:
41
40
  handle_sdk_error(e, fmt, "设置相机视角失败")
@@ -56,7 +55,7 @@ def cmd_camera_screenshot(fmt: str) -> None:
56
55
  }
57
56
 
58
57
  try:
59
- result = asyncio.run(_run())
58
+ result = run_cli_async(_run())
60
59
  output_result(result, fmt)
61
60
  except Exception as e:
62
61
  handle_sdk_error(e, fmt, "截图失败")
@@ -1,7 +1,5 @@
1
1
  """实体相关 CLI 命令。"""
2
2
 
3
- import asyncio
4
-
5
3
  from dimine_python_sdk.cli.utils._utils import (
6
4
  ENTITY_TYPE_CN,
7
5
  fmt_selected_item,
@@ -11,6 +9,7 @@ from dimine_python_sdk.cli.utils._utils import (
11
9
  output_result,
12
10
  parse_entity_ids,
13
11
  require_remote_sdk,
12
+ run_cli_async,
14
13
  safe_int,
15
14
  )
16
15
 
@@ -57,7 +56,7 @@ def cmd_get_selected(fmt: str, page_size: int, page: int) -> None:
57
56
  }
58
57
 
59
58
  try:
60
- result = asyncio.run(_run())
59
+ result = run_cli_async(_run())
61
60
  output_result(result, fmt)
62
61
  except Exception as e:
63
62
  handle_sdk_error(e, fmt, "获取选中实体失败")
@@ -100,7 +99,7 @@ def cmd_select(fmt: str, file_id: str | None, ids: str | None, mode: str) -> Non
100
99
  }
101
100
 
102
101
  try:
103
- result = asyncio.run(_run())
102
+ result = run_cli_async(_run())
104
103
  output_result(result, fmt)
105
104
  except Exception as e:
106
105
  handle_sdk_error(e, fmt, "选中操作失败")
@@ -127,7 +126,7 @@ def cmd_delete(fmt: str, file_id: str | None, ids: str) -> None:
127
126
  }
128
127
 
129
128
  try:
130
- result = asyncio.run(_run())
129
+ result = run_cli_async(_run())
131
130
  output_result(result, fmt)
132
131
  except Exception as e:
133
132
  handle_sdk_error(e, fmt, "删除实体失败")
@@ -162,7 +161,7 @@ def _set_visibility(fmt: str, file_id: str | None, raw_ids: str, action: str) ->
162
161
  }
163
162
 
164
163
  try:
165
- result = asyncio.run(_run())
164
+ result = run_cli_async(_run())
166
165
  output_result(result, fmt)
167
166
  except Exception as e:
168
167
  handle_sdk_error(e, fmt, "设置可见性失败")
@@ -1,7 +1,5 @@
1
1
  """要素/特征定义相关 CLI 命令。"""
2
2
 
3
- import asyncio
4
-
5
3
  from dimine_python_sdk.cli.utils._utils import (
6
4
  get_client,
7
5
  handle_sdk_error,
@@ -10,6 +8,7 @@ from dimine_python_sdk.cli.utils._utils import (
10
8
  parse_fields_arg,
11
9
  raise_or_exit,
12
10
  require_remote_sdk,
11
+ run_cli_async,
13
12
  )
14
13
 
15
14
 
@@ -38,7 +37,7 @@ def cmd_feature_list(fmt: str, file_id: str | None) -> None:
38
37
  return {"file_id": file_id, "feature_count": len(result), "features": result}
39
38
 
40
39
  try:
41
- result = asyncio.run(_run())
40
+ result = run_cli_async(_run())
42
41
  output_result(result, fmt)
43
42
  except SystemExit:
44
43
  raise
@@ -70,7 +69,7 @@ def cmd_feature_create(fmt: str, file_id: str, name: str, fields: str) -> None:
70
69
  }
71
70
 
72
71
  try:
73
- result = asyncio.run(_run())
72
+ result = run_cli_async(_run())
74
73
  output_result(result, fmt)
75
74
  except Exception as e:
76
75
  handle_sdk_error(e, fmt, "创建要素失败")
@@ -100,7 +99,7 @@ def cmd_feature_update(fmt: str, file_id: str, name: str, fields: str) -> None:
100
99
  }
101
100
 
102
101
  try:
103
- result = asyncio.run(_run())
102
+ result = run_cli_async(_run())
104
103
  output_result(result, fmt)
105
104
  except Exception as e:
106
105
  handle_sdk_error(e, fmt, "更新要素失败")
@@ -123,7 +122,7 @@ def cmd_feature_delete(fmt: str, file_id: str, name: str) -> None:
123
122
  }
124
123
 
125
124
  try:
126
- result = asyncio.run(_run())
125
+ result = run_cli_async(_run())
127
126
  output_result(result, fmt)
128
127
  except Exception as e:
129
128
  handle_sdk_error(e, fmt, "删除要素失败")
@@ -1,16 +1,14 @@
1
1
  """文件相关 CLI 命令。"""
2
2
 
3
- import asyncio
4
-
5
3
  from dimine_python_sdk.cli.utils._utils import (
6
4
  get_client,
7
5
  handle_sdk_error,
8
- open_client,
9
6
  output_error,
10
7
  output_result,
11
8
  raise_or_exit,
12
9
  require_remote_sdk,
13
10
  resolve_path,
11
+ run_cli_async,
14
12
  )
15
13
 
16
14
 
@@ -36,7 +34,7 @@ def cmd_file_tree(fmt: str) -> None:
36
34
  return {"file_count": len(result), "files": result}
37
35
 
38
36
  try:
39
- result = asyncio.run(_run())
37
+ result = run_cli_async(_run())
40
38
  output_result(result, fmt)
41
39
  except Exception as e:
42
40
  handle_sdk_error(e, fmt, "获取文件树失败")
@@ -52,21 +50,20 @@ def cmd_file_save(fmt: str, output_path: str, file_id: str | None) -> None:
52
50
 
53
51
  async def _run():
54
52
  nonlocal file_id
53
+ client = await get_client()
55
54
  if not file_id:
56
- async with open_client() as client:
57
- files = await client.get_files()
58
- if not files:
59
- raise_or_exit("没有打开的文件。请先在 Dimine 中打开文件,或提供 --file-id 参数。", fmt)
60
- if len(files) == 1:
61
- file_id = files[0].id
62
- else:
63
- file_list = "\n".join([f" - id: {f.id}, name: {f.name}" for f in files])
64
- raise_or_exit(
65
- f"当前打开了 {len(files)} 个文件,请指定 --file-id:\n{file_list}",
66
- fmt,
67
- )
68
- async with open_client() as client:
69
- await client.export_file(file_id=file_id, file_path=save_path)
55
+ files = await client.get_files()
56
+ if not files:
57
+ raise_or_exit("没有打开的文件。请先在 Dimine 中打开文件,或提供 --file-id 参数。", fmt)
58
+ if len(files) == 1:
59
+ file_id = files[0].id
60
+ else:
61
+ file_list = "\n".join([f" - id: {f.id}, name: {f.name}" for f in files])
62
+ raise_or_exit(
63
+ f"当前打开了 {len(files)} 个文件,请指定 --file-id:\n{file_list}",
64
+ fmt,
65
+ )
66
+ await client.export_file(file_id=file_id, file_path=save_path)
70
67
  return {
71
68
  "success": True,
72
69
  "file_id": file_id,
@@ -75,7 +72,7 @@ def cmd_file_save(fmt: str, output_path: str, file_id: str | None) -> None:
75
72
  }
76
73
 
77
74
  try:
78
- result = asyncio.run(_run())
75
+ result = run_cli_async(_run())
79
76
  output_result(result, fmt)
80
77
  except SystemExit:
81
78
  raise
@@ -84,7 +81,7 @@ def cmd_file_save(fmt: str, output_path: str, file_id: str | None) -> None:
84
81
 
85
82
 
86
83
  def cmd_file_open(fmt: str, path: str) -> None:
87
- """在 Dimine 中打开文件(需 Dimine 运行)。"""
84
+ """在 Dimine 中打开文件(需 Dimine 运行)。如果文件已在 Dimine 中打开,先关闭再重新打开。"""
88
85
  err = require_remote_sdk()
89
86
  if err:
90
87
  output_error(err, fmt)
@@ -93,6 +90,16 @@ def cmd_file_open(fmt: str, path: str) -> None:
93
90
 
94
91
  async def _run():
95
92
  client = await get_client()
93
+
94
+ # 检查是否有同名文件已打开,如果有则先关闭
95
+ target_name = path
96
+ opened_files = await client.get_files()
97
+ for f in opened_files:
98
+ if f.name == target_name:
99
+ await client.close_file(f.id)
100
+ break
101
+
102
+ # 打开文件
96
103
  await client.open_file(file_path)
97
104
  return {
98
105
  "success": True,
@@ -101,7 +108,7 @@ def cmd_file_open(fmt: str, path: str) -> None:
101
108
  }
102
109
 
103
110
  try:
104
- result = asyncio.run(_run())
111
+ result = run_cli_async(_run())
105
112
  output_result(result, fmt)
106
113
  except Exception as e:
107
114
  handle_sdk_error(e, fmt, "打开文件失败")
@@ -123,7 +130,7 @@ def cmd_file_close(fmt: str, file_id: str) -> None:
123
130
  }
124
131
 
125
132
  try:
126
- result = asyncio.run(_run())
133
+ result = run_cli_async(_run())
127
134
  output_result(result, fmt)
128
135
  except Exception as e:
129
136
  handle_sdk_error(e, fmt, "关闭文件失败")
@@ -147,7 +154,7 @@ def cmd_file_create(fmt: str, path: str) -> None:
147
154
  }
148
155
 
149
156
  try:
150
- result = asyncio.run(_run())
157
+ result = run_cli_async(_run())
151
158
  output_result(result, fmt)
152
159
  except Exception as e:
153
160
  handle_sdk_error(e, fmt, "创建文件失败")
@@ -180,7 +187,7 @@ def cmd_file_visibility(fmt: str, file_id: str, action: str) -> None:
180
187
  }
181
188
 
182
189
  try:
183
- result = asyncio.run(_run())
190
+ result = run_cli_async(_run())
184
191
  output_result(result, fmt)
185
192
  except Exception as e:
186
193
  handle_sdk_error(e, fmt, "设置文件可见性失败")
@@ -1,6 +1,5 @@
1
1
  """几何体相关 CLI 命令。"""
2
2
 
3
- import asyncio
4
3
  import sys
5
4
 
6
5
  from dimine_python_sdk.cli.utils._utils import (
@@ -13,6 +12,7 @@ from dimine_python_sdk.cli.utils._utils import (
13
12
  parse_color,
14
13
  output_result,
15
14
  require_remote_sdk,
15
+ run_cli_async,
16
16
  )
17
17
 
18
18
 
@@ -308,9 +308,9 @@ def _build_partial_geometry_objects(geometries: list) -> list:
308
308
  return result
309
309
 
310
310
 
311
- def cmd_geometry_get(fmt: str, file_id: str, layer_id: str | None,
312
- entity_types: str | None = None, features: str | None = None,
313
- with_geometry: bool = False) -> None:
311
+ def cmd_geometry_list(fmt: str, file_id: str, layer_id: str | None,
312
+ entity_types: str | None = None, features: str | None = None,
313
+ with_geometry: bool = False) -> None:
314
314
  """获取图层中的几何体对象(需 Dimine 运行)。
315
315
 
316
316
  默认只返回几何体的基础信息(id、file、layer、feature、type、color、properties)。
@@ -366,7 +366,7 @@ def cmd_geometry_get(fmt: str, file_id: str, layer_id: str | None,
366
366
  }
367
367
 
368
368
  try:
369
- result = asyncio.run(_run())
369
+ result = run_cli_async(_run())
370
370
  output_result(result, fmt)
371
371
  except Exception as e:
372
372
  handle_sdk_error(e, fmt, "获取几何体失败")
@@ -423,7 +423,7 @@ def cmd_geometry_create(fmt: str, geometries: str | None = None,
423
423
  }
424
424
 
425
425
  try:
426
- result = asyncio.run(_run())
426
+ result = run_cli_async(_run())
427
427
  output_result(result, fmt)
428
428
  except Exception as e:
429
429
  handle_sdk_error(e, fmt, "创建几何体失败")
@@ -483,7 +483,7 @@ def cmd_geometry_update(fmt: str, geometries: str | None = None,
483
483
  }
484
484
 
485
485
  try:
486
- result = asyncio.run(_run())
486
+ result = run_cli_async(_run())
487
487
  output_result(result, fmt)
488
488
  except Exception as e:
489
489
  handle_sdk_error(e, fmt, "更新几何体失败")
@@ -557,7 +557,7 @@ def cmd_geometry_upsert(fmt: str, geometries: str | None = None,
557
557
  }
558
558
 
559
559
  try:
560
- result = asyncio.run(_run())
560
+ result = run_cli_async(_run())
561
561
  output_result(result, fmt)
562
562
  except Exception as e:
563
563
  handle_sdk_error(e, fmt, "处理几何体失败")
@@ -610,7 +610,7 @@ def register_commands(subparsers, format_parent) -> None:
610
610
 
611
611
 
612
612
  COMMAND_HANDLERS = {
613
- "geometry-list": lambda args: cmd_geometry_get(args.format, args.file_id, args.layer_id,
613
+ "geometry-list": lambda args: cmd_geometry_list(args.format, args.file_id, args.layer_id,
614
614
  args.entity_types, args.features,
615
615
  args.with_geometry),
616
616
  "geometry-create": lambda args: cmd_geometry_create(args.format, args.geometries,
@@ -2,7 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import asyncio
6
5
  from typing import TYPE_CHECKING
7
6
 
8
7
  if TYPE_CHECKING:
@@ -13,6 +12,7 @@ from dimine_python_sdk.cli.utils._utils import (
13
12
  get_client,
14
13
  output_error,
15
14
  output_result,
15
+ run_cli_async,
16
16
  require_local_sdk,
17
17
  require_remote_sdk,
18
18
  safe_float,
@@ -170,7 +170,7 @@ def cmd_tunnel(fmt: str, section_type: int, width: float, height: float | None,
170
170
  if err:
171
171
  output_error(f"未提供 --center-lines,需要从 Dimine 获取选中线段: {err}", fmt)
172
172
  try:
173
- lines = asyncio.run(_fetch_center_lines_from_selection())
173
+ lines = run_cli_async(_fetch_center_lines_from_selection())
174
174
  except Exception as e:
175
175
  output_error(f"获取选中线段失败: {e}", fmt)
176
176
  else:
@@ -228,7 +228,7 @@ def cmd_tunnel(fmt: str, section_type: int, width: float, height: float | None,
228
228
  }
229
229
 
230
230
  if import_to_dimine:
231
- import_result = asyncio.run(_import_to_dimine(geometries))
231
+ import_result = run_cli_async(_import_to_dimine(geometries))
232
232
  result["imported"] = import_result
233
233
  else:
234
234
  # 仅在明确不导入时返回简化模型摘要,不含原始坐标
@@ -1,7 +1,5 @@
1
1
  """图层相关 CLI 命令。"""
2
2
 
3
- import asyncio
4
-
5
3
  from dimine_python_sdk.cli.utils._utils import (
6
4
  get_client,
7
5
  handle_sdk_error,
@@ -9,6 +7,7 @@ from dimine_python_sdk.cli.utils._utils import (
9
7
  output_result,
10
8
  raise_or_exit,
11
9
  require_remote_sdk,
10
+ run_cli_async,
12
11
  )
13
12
 
14
13
 
@@ -31,7 +30,7 @@ def cmd_layers(fmt: str, file_id: str | None) -> None:
31
30
  return {"file_id": file_id, "layer_count": len(result), "layers": result}
32
31
 
33
32
  try:
34
- result = asyncio.run(_run())
33
+ result = run_cli_async(_run())
35
34
  output_result(result, fmt)
36
35
  except SystemExit:
37
36
  raise
@@ -63,7 +62,7 @@ def cmd_layer_create(fmt: str, name: str, file_id: str | None) -> None:
63
62
  }
64
63
 
65
64
  try:
66
- result = asyncio.run(_run())
65
+ result = run_cli_async(_run())
67
66
  output_result(result, fmt)
68
67
  except SystemExit:
69
68
  raise
@@ -99,7 +98,7 @@ def cmd_layer_visibility(fmt: str, file_id: str, layer_name: str, action: str) -
99
98
  }
100
99
 
101
100
  try:
102
- result = asyncio.run(_run())
101
+ result = run_cli_async(_run())
103
102
  output_result(result, fmt)
104
103
  except Exception as e:
105
104
  handle_sdk_error(e, fmt, "设置图层可见性失败")
@@ -128,7 +127,7 @@ def cmd_layer_delete(fmt: str, name: str, file_id: str | None) -> None:
128
127
  }
129
128
 
130
129
  try:
131
- result = asyncio.run(_run())
130
+ result = run_cli_async(_run())
132
131
  output_result(result, fmt)
133
132
  except SystemExit:
134
133
  raise
@@ -1,7 +1,5 @@
1
1
  """文字标注相关 CLI 命令。"""
2
2
 
3
- import asyncio
4
-
5
3
  from dimine_python_sdk.cli.utils._utils import (
6
4
  get_client,
7
5
  handle_sdk_error,
@@ -11,8 +9,8 @@ from dimine_python_sdk.cli.utils._utils import (
11
9
  parse_json_arg,
12
10
  parse_point3d,
13
11
  require_remote_sdk,
12
+ run_cli_async,
14
13
  )
15
- from dimine_python_sdk.models.types import TextInfo
16
14
 
17
15
 
18
16
  def cmd_text_add(
@@ -37,6 +35,8 @@ def cmd_text_add(
37
35
  text_infos = []
38
36
  if geometries is not None:
39
37
  # 批量模式:从 JSON 解析完整的 TextInfo 列表
38
+ from dimine_python_sdk.models.types import TextInfo
39
+
40
40
  items = parse_json_arg(geometries, "geometries")
41
41
  if not isinstance(items, list):
42
42
  raise ValueError("--geometries 必须是 JSON 数组格式")
@@ -76,7 +76,7 @@ def cmd_text_add(
76
76
  }
77
77
 
78
78
  try:
79
- result = asyncio.run(_run())
79
+ result = run_cli_async(_run())
80
80
  output_result(result, fmt)
81
81
  except Exception as e:
82
82
  handle_sdk_error(e, fmt, "添加文字标注失败")
@@ -8,60 +8,77 @@
8
8
  - JSON/文本输出格式化
9
9
  """
10
10
 
11
- import asyncio
11
+ from __future__ import annotations
12
+
12
13
  import json
13
14
  import os
14
15
  import re
15
16
  import sys
16
- from typing import Any, Optional
17
+ from typing import TYPE_CHECKING, Any, Optional
17
18
 
18
- from dimine_python_sdk.conn.conn_client import ConnClient
19
+ if TYPE_CHECKING:
20
+ from dimine_python_sdk.conn.conn_client import ConnClient
19
21
 
20
22
  # ==============================================================================
21
23
  # SDK 状态检查
22
24
  # ==============================================================================
23
25
 
24
- _SDK_AVAILABLE = False
25
- _local_sdk_available = False
26
+ _SDK_AVAILABLE: bool | None = None # None = 尚未检查
27
+ _local_sdk_available: bool | None = None # None = 尚未检查
26
28
  _import_error: str = ""
27
- open_client = None # type: ignore[assignment]
28
-
29
- try:
30
- from dimine_python_sdk.conn.conn_client import open_client as _open_client # type: ignore[no-redef]
31
- open_client = _open_client
32
- _SDK_AVAILABLE = True
33
- except ImportError as e:
34
- _import_error = str(e)
35
- except Exception as e:
36
- _import_error = str(e)
37
-
38
- try:
39
- from dimine_python_sdk.lib.native._core import Dm # noqa: F401
40
- _local_sdk_available = True
41
- except ImportError:
42
- pass
43
- except Exception:
44
- pass
29
+ _open_client: Any = None # 惰性缓存的 open_client 函数(私有,勿直接 import)
30
+
31
+
32
+ def _check_remote_sdk() -> bool:
33
+ """惰性检查远程 SDK 是否可用。首次调用时导入 conn 模块链并缓存结果。"""
34
+ global _SDK_AVAILABLE, _import_error, _open_client
35
+ if _SDK_AVAILABLE is not None:
36
+ return _SDK_AVAILABLE
37
+ try:
38
+ from dimine_python_sdk.conn.conn_client import open_client as _conn_open_client
39
+ _open_client = _conn_open_client
40
+ _SDK_AVAILABLE = True
41
+ except ImportError as e:
42
+ _import_error = str(e)
43
+ _SDK_AVAILABLE = False
44
+ except Exception as e:
45
+ _import_error = str(e)
46
+ _SDK_AVAILABLE = False
47
+ return _SDK_AVAILABLE
48
+
49
+
50
+ def _check_local_sdk() -> bool:
51
+ """惰性检查本地 SDK 是否可用。首次调用时加载 DmPyBindInterface.pyd 并缓存结果。"""
52
+ global _local_sdk_available
53
+ if _local_sdk_available is not None:
54
+ return _local_sdk_available
55
+ try:
56
+ from dimine_python_sdk.lib.native._core import Dm # noqa: F401
57
+ _local_sdk_available = True
58
+ except (ImportError, Exception):
59
+ _local_sdk_available = False
60
+ return _local_sdk_available
45
61
 
46
62
 
47
63
  def is_remote_sdk_available() -> bool:
48
64
  """返回远程 SDK 是否可用。"""
49
- return _SDK_AVAILABLE
65
+ return _check_remote_sdk()
50
66
 
51
67
 
52
68
  def is_local_sdk_available() -> bool:
53
69
  """返回本地 SDK 是否可用。"""
54
- return _local_sdk_available
70
+ return _check_local_sdk()
55
71
 
56
72
 
57
73
  def get_import_error() -> str:
58
74
  """返回 SDK 导入错误信息。"""
75
+ _check_remote_sdk() # 确保已检查
59
76
  return _import_error
60
77
 
61
78
 
62
79
  def require_remote_sdk() -> str:
63
80
  """返回远程 SDK 不可用时的错误消息(空字符串表示可用)。"""
64
- if _SDK_AVAILABLE:
81
+ if _check_remote_sdk():
65
82
  return ""
66
83
  return (
67
84
  f"Dimine Python SDK 不可用: {_import_error}\n\n"
@@ -75,7 +92,7 @@ def require_remote_sdk() -> str:
75
92
 
76
93
  def require_local_sdk() -> str:
77
94
  """返回本地 SDK 不可用时的错误消息(空字符串表示可用)。"""
78
- if _local_sdk_available:
95
+ if _check_local_sdk():
79
96
  return ""
80
97
  return (
81
98
  f"Dimine Python SDK 本地模块不可用。\n"
@@ -94,12 +111,37 @@ _client_instance: Optional[Any] = None
94
111
  _client_loop_id: Optional[int] = None
95
112
 
96
113
 
114
+ async def _run_with_cleanup(coro):
115
+ """包装协程,确保在事件循环关闭前清理 WebSocket 客户端。
116
+
117
+ 防止 Windows ProactorEventLoop 关闭时因未清理的 IOCP 句柄导致
118
+ 进程退出码变为 0xC0000409 (-1073740791)。
119
+ """
120
+ try:
121
+ return await coro
122
+ finally:
123
+ await close_client()
124
+
125
+
126
+ def run_cli_async(coro):
127
+ """CLI 命令统一入口:运行异步操作并确保客户端清理。
128
+
129
+ 替代裸 asyncio.run(),在所有 CLI 命令的 _run() 之后调用。
130
+ 用法:result = run_cli_async(_run())
131
+ """
132
+ import asyncio
133
+
134
+ return asyncio.run(_run_with_cleanup(coro))
135
+
136
+
97
137
  async def get_client()->ConnClient:
98
138
  """获取或创建共享的 WebSocket 客户端,避免每次操作都建立新连接。
99
139
 
100
140
  如果检测到事件循环已变更(如 asyncio.run() 多次调用场景),
101
141
  自动丢弃旧连接并重新建立,避免跨事件循环使用 WebSocket 导致的 RuntimeError。
102
142
  """
143
+ import asyncio
144
+
103
145
  global _client_ctx, _client_instance, _client_loop_id
104
146
 
105
147
  # 检测事件循环是否变更(asyncio.run() 每次创建新的事件循环)
@@ -115,7 +157,7 @@ async def get_client()->ConnClient:
115
157
  _client_loop_id = None
116
158
 
117
159
  if _client_instance is None:
118
- _client_ctx = open_client()
160
+ _client_ctx = _open_client()
119
161
  _client_instance = await _client_ctx.__aenter__()
120
162
  _client_loop_id = id(asyncio.get_running_loop())
121
163
  return _client_instance
@@ -19,6 +19,7 @@ class BaseConnClient:
19
19
  model_config = {"arbitrary_types_allowed": True}
20
20
 
21
21
  url: Optional[str] = "ws://127.0.0.1:8000"
22
+ max_size: Optional[int] = None
22
23
  is_connected: Optional[bool] = False
23
24
  pending_requests: Dict[str, Tuple[asyncio.Future, Callable]] = field(default_factory=dict)
24
25
  event_handlers: Dict[str, List[Callable]] = field(default_factory=dict)
@@ -106,7 +107,10 @@ class BaseConnClient:
106
107
 
107
108
  async def connect(self)-> bool:
108
109
  try:
109
- self.websocket = await websockets.connect(self.url, close_timeout=1)
110
+ connect_kwargs: Dict[str, Any] = {"close_timeout": 1}
111
+ if self.max_size is not None:
112
+ connect_kwargs["max_size"] = self.max_size
113
+ self.websocket = await websockets.connect(self.url, **connect_kwargs)
110
114
  self.is_connected = True
111
115
  logger.info("success connected to %s", self.url)
112
116
  if self.receive_task is None or self.receive_task.done():