dimine-python-sdk 0.2.1__py3-none-any.whl → 0.2.2__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 (37) hide show
  1. dimine_python_sdk/__init__.py +1 -1
  2. dimine_python_sdk/cli/commands/file.py +2 -2
  3. dimine_python_sdk/docs/guides/lib/exploitation/contour_modeling.md +112 -5
  4. dimine_python_sdk/lib/exploitation/__init__.py +6 -0
  5. dimine_python_sdk/lib/exploitation/contour_modeling.py +190 -0
  6. dimine_python_sdk/lib/exploitation/models.py +16 -0
  7. dimine_python_sdk/lib/io/dmf_file.py +13 -11
  8. dimine_python_sdk/lib/io/models.py +2 -2
  9. dimine_python_sdk/lib/native/__init__.py +2 -0
  10. dimine_python_sdk/lib/native/exploitation.py +62 -0
  11. dimine_python_sdk/lib/native/geometry.py +18 -8
  12. dimine_python_sdk/lib/native/models.py +1 -0
  13. dimine_python_sdk/site/guides/lib/exploitation/contour_modeling.html +281 -17
  14. dimine_python_sdk/site/objects.inv +0 -0
  15. dimine_python_sdk/site/reference/cli/commands/file.html +2 -2
  16. dimine_python_sdk/site/reference/lib/bridge.html +472 -228
  17. dimine_python_sdk/site/reference/lib/exploitation/api.html +727 -269
  18. dimine_python_sdk/site/reference/lib/exploitation/contour_modeling.html +820 -357
  19. dimine_python_sdk/site/reference/lib/exploitation/models.html +115 -0
  20. dimine_python_sdk/site/reference/lib/io/dmf_file.html +47 -43
  21. dimine_python_sdk/site/reference/lib/io/models.html +5 -5
  22. dimine_python_sdk/site/reference/lib/native/exploitation.html +210 -24
  23. dimine_python_sdk/site/reference/lib/native/geometry.html +222 -200
  24. dimine_python_sdk/site/reference/lib/native/models.html +49 -13
  25. dimine_python_sdk/site/search/search_index.json +1 -1
  26. dimine_python_sdk/site/sitemap.xml +82 -82
  27. dimine_python_sdk/site/sitemap.xml.gz +0 -0
  28. {dimine_python_sdk-0.2.1.dist-info → dimine_python_sdk-0.2.2.dist-info}/METADATA +2 -1
  29. {dimine_python_sdk-0.2.1.dist-info → dimine_python_sdk-0.2.2.dist-info}/RECORD +37 -33
  30. {dimine_python_sdk-0.2.1.dist-info → dimine_python_sdk-0.2.2.dist-info}/WHEEL +1 -1
  31. outline/contour_extract.py +450 -0
  32. outline/contour_match.py +1095 -0
  33. outline/contour_pipeline.py +586 -0
  34. outline/contour_validate.py +645 -0
  35. outline/read_dwg.py +1 -1
  36. {dimine_python_sdk-0.2.1.dist-info → dimine_python_sdk-0.2.2.dist-info}/entry_points.txt +0 -0
  37. {dimine_python_sdk-0.2.1.dist-info → dimine_python_sdk-0.2.2.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,7 @@ from pathlib import Path
3
3
  from .conn import ConnClient
4
4
 
5
5
  DOCS_PATH = Path(__file__).parent / "docs"
6
- __VERSION__ = "0.2.1" # 由 build.bat 中的 generate_version.py 自动更新
6
+ __VERSION__ = "0.2.2" # 由 build.bat 中的 generate_version.py 自动更新
7
7
 
8
8
 
9
9
  __all__ = [
@@ -195,13 +195,13 @@ def register_commands(subparsers, format_parent) -> None:
195
195
  p.add_argument("--file-id", default=None, help="文件ID,多个文件打开时必填")
196
196
 
197
197
  p = subparsers.add_parser("file-open", parents=[format_parent], help="打开文件到 Dimine 场景")
198
- p.add_argument("--path", required=True, help="要打开的 .dmf/.dwg/.dxf 等文件路径")
198
+ p.add_argument("--path", "--file", required=True, help="要打开的 .dmf/.dwg/.dxf 等文件路径")
199
199
 
200
200
  p = subparsers.add_parser("file-close", parents=[format_parent], help="关闭 Dimine 中的文件")
201
201
  p.add_argument("--file-id", required=True, help="要关闭的文件ID")
202
202
 
203
203
  p = subparsers.add_parser("file-create", parents=[format_parent], help="在 Dimine 中创建新的空文件")
204
- p.add_argument("--path", required=True, help="新文件的路径")
204
+ p.add_argument("--path", "--file", required=True, help="新文件的路径")
205
205
 
206
206
  p = subparsers.add_parser("file-visibility", parents=[format_parent], help="设置文件显示/隐藏状态")
207
207
  p.add_argument("--file-id", required=True, help="文件ID")
@@ -331,7 +331,105 @@ for i, merged in enumerate(merged_results):
331
331
  print(f" 实体[{i}]: 顶点={merged.points.shape[0]}, 面片={merged.faces.shape[0]}")
332
332
  ```
333
333
 
334
- ## 6. 算法类型
334
+ ## 6. 地表建模
335
+
336
+ 从多条等高线生成三角网地表模型(DTM)。输入为一组等高线,输出为三角网地表。
337
+
338
+ ### 基础用法
339
+
340
+ ```python
341
+ import numpy as np
342
+ from dimine_python_sdk.lib.exploitation import SurfaceModeling
343
+
344
+ # 多条等高线(不同高程的封闭轮廓线)
345
+ contour1 = [[0, 0, 100], [10, 0, 100], [10, 10, 100], [0, 10, 100], [0, 0, 100]]
346
+ contour2 = [[2, 2, 200], [8, 2, 200], [8, 8, 200], [2, 8, 200], [2, 2, 200]]
347
+ contour3 = [[4, 4, 300], [6, 4, 300], [6, 6, 300], [4, 6, 300], [4, 4, 300]]
348
+
349
+ # 地表建模
350
+ surfaces = SurfaceModeling.create([contour1, contour2, contour3], mode=0)
351
+
352
+ print(f"生成地表数量: {len(surfaces)}")
353
+ for i, tin in enumerate(surfaces):
354
+ print(f" 地表[{i}] 顶点数: {tin.points.shape[0]}, 三角面片数: {tin.faces.shape[0]}")
355
+ ```
356
+
357
+ ### 支持的输入格式
358
+
359
+ `SurfaceModeling.create(contour_lines, mode=0)` 的每条等高线支持三种输入格式:
360
+
361
+ ```python
362
+ import numpy as np
363
+ from dimine_python_sdk.models.types import Line
364
+ from dimine_python_sdk.lib.exploitation import SurfaceModeling
365
+
366
+ # 方式一:List[List[List[float]]] 纯列表
367
+ result = SurfaceModeling.create([
368
+ [[0, 0, 100], [10, 0, 100], [10, 10, 100], [0, 10, 100], [0, 0, 100]],
369
+ [[2, 2, 200], [8, 2, 200], [8, 8, 200], [2, 8, 200], [2, 2, 200]],
370
+ ])
371
+
372
+ # 方式二:List[np.ndarray]
373
+ arr1 = np.array([[0, 0, 100], [10, 0, 100], [10, 10, 100], [0, 10, 100], [0, 0, 100]])
374
+ arr2 = np.array([[2, 2, 200], [8, 2, 200], [8, 8, 200], [2, 8, 200], [2, 2, 200]])
375
+ result = SurfaceModeling.create([arr1, arr2])
376
+
377
+ # 方式三:List[Line Pydantic 模型]
378
+ line1 = Line(geometry=[[0, 0, 100], [10, 0, 100], [10, 10, 100], [0, 10, 100], [0, 0, 100]])
379
+ line2 = Line(geometry=[[2, 2, 200], [8, 2, 200], [8, 8, 200], [2, 8, 200], [2, 2, 200]])
380
+ result = SurfaceModeling.create([line1, line2])
381
+ ```
382
+
383
+ ### 从 DMF 文件读取等高线进行地表建模
384
+
385
+ ```python
386
+ from dimine_python_sdk.lib.io import DmfFile
387
+ from dimine_python_sdk.lib.exploitation import SurfaceModeling
388
+
389
+ # 从 DMF 文件加载等高线
390
+ dmf = DmfFile("path/to/contours.dmf")
391
+
392
+ # 提取所有多段线实体作为等高线
393
+ contour_lines = []
394
+ for layer in dmf.layers:
395
+ for entity in layer.entities:
396
+ if hasattr(entity, 'geometry'):
397
+ contour_lines.append(entity.geometry)
398
+
399
+ # 执行地表建模
400
+ surfaces = SurfaceModeling.create(contour_lines, mode=0)
401
+
402
+ print(f"从 {len(contour_lines)} 条等高线生成 {len(surfaces)} 个地表模型")
403
+ ```
404
+
405
+ ### 参数说明
406
+
407
+ | 参数 | 类型 | 默认值 | 说明 |
408
+ |------|------|--------|------|
409
+ | `contour_lines` | `List[Line]` / `List[np.ndarray]` / `List[List[List[float]]]` | — | 等高线列表,每条为三维点序列 |
410
+ | `mode` | `int` | `0` | 建模模式,默认 0 |
411
+ | `vertical_angle_threshold` | `float` | `25.0` | 伪影判定阈值(度):三角面法向量与水平面夹角小于此值则判定为伪影 |
412
+ | `vertical_iterations` | `int` | `10` | 伪影剥离最大迭代次数,某轮无新伪影则提前退出 |
413
+
414
+ ### 伪影移除原理
415
+
416
+ Delaunay 三角网在凸包边界处会生成近似垂直的伪影三角形。`SurfaceModeling.create`
417
+ 自动通过迭代剥离移除这些伪影:
418
+
419
+ 1. 找出严格边界三角形(包含仅被一个三角形引用的边)
420
+ 2. 扩展为边界区域(边界三角形 + 与之共享边的邻接三角形)
421
+ 3. 对边界区域内三角面做法向量检查:\|法向量Z\| < sin(threshold) → 移除
422
+ 4. 移除后形成新边界,重复步骤 1-3,直到某轮无新伪影或达到最大迭代次数
423
+
424
+ ```python
425
+ # 默认参数(推荐)
426
+ surfaces = SurfaceModeling.create(contour_lines, mode=0)
427
+
428
+ # 调整阈值(更激进 / 更保守)
429
+ surfaces = SurfaceModeling.create(contour_lines, mode=0, vertical_angle_threshold=30.0)
430
+ ```
431
+
432
+ ## 7. 算法类型
335
433
 
336
434
  通过 `ContourAlgorithm` 枚举指定两轮廓线之间的连接算法:
337
435
 
@@ -354,19 +452,21 @@ models = ContourModeling.two_contour_modeling(
354
452
  )
355
453
  ```
356
454
 
357
- ## 7. 异常处理
455
+ ## 8. 异常处理
358
456
 
359
457
  轮廓线建模定义了层次化异常,建议调用时捕获具体异常:
360
458
 
361
459
  ```python
362
460
  from dimine_python_sdk.lib.exploitation import (
363
461
  ContourModeling,
462
+ SurfaceModeling,
364
463
  ContourAlgorithm,
365
464
  ModelingError,
366
465
  TwoContourModelingError,
367
466
  SingleContourModelingError,
368
467
  ContourExtrapolateModelingError,
369
468
  ParallelContourModelingError,
469
+ SurfaceModelingError,
370
470
  )
371
471
 
372
472
  try:
@@ -377,6 +477,12 @@ except TwoContourModelingError as e:
377
477
  print(f"两轮廓线建模失败: {e}")
378
478
  except ModelingError as e:
379
479
  print(f"轮廓线建模错误: {e}")
480
+
481
+ # 地表建模异常处理
482
+ try:
483
+ surfaces = SurfaceModeling.create(contour_lines, mode=0)
484
+ except SurfaceModelingError as e:
485
+ print(f"地表建模失败: {e}")
380
486
  ```
381
487
 
382
488
  异常继承关系:
@@ -387,10 +493,11 @@ RuntimeError
387
493
  ├── TwoContourModelingError (两轮廓线建模失败)
388
494
  ├── SingleContourModelingError (单轮廓线封闭为面失败)
389
495
  ├── ContourExtrapolateModelingError (轮廓线外推建模失败)
390
- └── ParallelContourModelingError (多平行轮廓线建模失败)
496
+ ├── ParallelContourModelingError (多平行轮廓线建模失败)
497
+ └── SurfaceModelingError (地表建模失败)
391
498
  ```
392
499
 
393
- ## 8. 提交到 Dimine 场景
500
+ ## 9. 提交到 Dimine 场景
394
501
 
395
502
  生成的三角网模型可以通过 `conn` 模块提交到 Dimine 场景中显示:
396
503
 
@@ -435,7 +542,7 @@ if __name__ == "__main__":
435
542
  asyncio.run(main())
436
543
  ```
437
544
 
438
- ## 9. 完整示例
545
+ ## 10. 完整示例
439
546
 
440
547
  ```python
441
548
  import numpy as np
@@ -8,6 +8,8 @@ from dimine_python_sdk.lib.exploitation.contour_modeling import (
8
8
  ModelingError,
9
9
  ParallelContourModelingError,
10
10
  SingleContourModelingError,
11
+ SurfaceModeling,
12
+ SurfaceModelingError,
11
13
  TwoContourModelingError,
12
14
  )
13
15
  from dimine_python_sdk.lib.exploitation.implicit_modeling import (
@@ -31,6 +33,7 @@ from dimine_python_sdk.lib.exploitation.models import (
31
33
  ImplicitStratumModelingParam,
32
34
  LanewayParam,
33
35
  SectionParam,
36
+ SurfaceModelingParam,
34
37
  )
35
38
 
36
39
  __all__ = [
@@ -45,10 +48,12 @@ __all__ = [
45
48
  "TwoContourModelingError",
46
49
  "SingleContourModelingError",
47
50
  "ContourExtrapolateModelingError",
51
+ "SurfaceModelingError",
48
52
  "ParallelContourModelingError",
49
53
  "ContourAlgorithm",
50
54
  "ExtrapolateMode",
51
55
  "ExtrapolateDirection",
56
+ "SurfaceModeling",
52
57
  "ImplicitModeling",
53
58
  "ImplicitModelingError",
54
59
  "ImplicitStratumModelingError",
@@ -56,4 +61,5 @@ __all__ = [
56
61
  "ImplicitGridParam",
57
62
  "ImplicitEstimationParam",
58
63
  "ImplicitStratumModelingParam",
64
+ "SurfaceModelingParam",
59
65
  ]
@@ -23,6 +23,7 @@ from dimine_python_sdk.lib.native.exploitation import (
23
23
  contour_extrapolate_modeling as _native_contour_extrapolate_modeling,
24
24
  multi_parallel_contour_modeling as _native_multi_parallel_contour_modeling,
25
25
  single_contour_modeling as _native_single_contour_modeling,
26
+ surface_modeling as _native_surface_modeling,
26
27
  two_contour_modeling as _native_two_contour_modeling,
27
28
  unite_polydata as _native_unite_polydata,
28
29
  )
@@ -35,12 +36,15 @@ __all__ = [
35
36
  "SingleContourModelingError",
36
37
  "ContourExtrapolateModelingError",
37
38
  "ParallelContourModelingError",
39
+ "SurfaceModelingError",
38
40
  "ContourModeling",
41
+ "SurfaceModeling",
39
42
  "two_contour_modeling_impl",
40
43
  "single_contour_modeling_impl",
41
44
  "contour_extrapolate_modeling_impl",
42
45
  "unite_polydata_impl",
43
46
  "multi_parallel_contour_modeling_impl",
47
+ "surface_modeling_impl",
44
48
  ]
45
49
 
46
50
 
@@ -64,11 +68,124 @@ class ParallelContourModelingError(ModelingError):
64
68
  """多平行轮廓线建模失败"""
65
69
 
66
70
 
71
+ class SurfaceModelingError(ModelingError):
72
+ """地表建模失败"""
73
+
74
+
67
75
  def _native_tin_to_tingeometry(tin: NativeTin) -> TINGeometry:
68
76
  """将 NativeTin 转换为 TINGeometry"""
69
77
  return TINGeometry(points=tin.points, faces=tin.faces)
70
78
 
71
79
 
80
+ def _remove_vertical_triangles(
81
+ tin: NativeTin,
82
+ angle_threshold: float = 25.0,
83
+ iterations: int = 10,
84
+ ) -> NativeTin:
85
+ """移除地表模型中凸包边界附近的伪影三角形。
86
+
87
+ 这些三角形出现在 Delaunay 三角网凸包边界附近,由边界边连接远距离
88
+ 顶点形成近似垂直的三角面,并非真实地形表面的一部分。
89
+
90
+ 每次迭代:
91
+ 1. 找出严格边界三角形(包含只被一个三角形引用的边)
92
+ 2. 边界三角形 + 与之共享边的邻接三角形共同构成「边界区域」
93
+ 3. 对边界区域内的三角形做法向量角度检查(默认 15°)
94
+ 4. 移除不合规三角形,形成新的边界
95
+ 5. 重复 1-4,逐步向内剥离伪影层
96
+
97
+ Parameters
98
+ ----------
99
+ tin : NativeTin
100
+ 待处理的三角网模型。
101
+ angle_threshold : float
102
+ 法向量与水平面的夹角阈值(度),默认 15.0。
103
+ iterations : int
104
+ 最大迭代次数,默认 3。若某轮未移除任何三角形则提前终止。
105
+
106
+ Returns
107
+ -------
108
+ NativeTin
109
+ 剔除伪影后的三角网模型,顶点已重新编号。
110
+ """
111
+ if len(tin.faces) == 0:
112
+ return tin
113
+
114
+ points = np.asarray(tin.points, dtype=float)
115
+ faces = np.asarray(tin.faces, dtype=int)
116
+ min_nz = np.sin(np.radians(angle_threshold))
117
+
118
+ for _ in range(iterations):
119
+ n_faces = len(faces)
120
+ if n_faces == 0:
121
+ break
122
+
123
+ # --- 构建边→三角形映射,找出边界边和严格边界三角形 ---
124
+ edge_to_tris: dict[tuple, list] = {}
125
+ for fi, f in enumerate(faces):
126
+ i0, i1, i2 = int(f[0]), int(f[1]), int(f[2])
127
+ for e in [(min(i0, i1), max(i0, i1)),
128
+ (min(i1, i2), max(i1, i2)),
129
+ (min(i2, i0), max(i2, i0))]:
130
+ edge_to_tris.setdefault(e, []).append(fi)
131
+
132
+ boundary_tri_set: set = set()
133
+ for tris in edge_to_tris.values():
134
+ if len(tris) == 1:
135
+ boundary_tri_set.update(tris)
136
+
137
+ if not boundary_tri_set:
138
+ break
139
+
140
+ # --- 扩展边界区域:纳入与边界三角形共享边的邻接三角形 ---
141
+ boundary_region: set = set(boundary_tri_set)
142
+ for tri_idx in list(boundary_tri_set):
143
+ f = faces[tri_idx]
144
+ i0, i1, i2 = int(f[0]), int(f[1]), int(f[2])
145
+ for e in [(min(i0, i1), max(i0, i1)),
146
+ (min(i1, i2), max(i1, i2)),
147
+ (min(i2, i0), max(i2, i0))]:
148
+ for neighbor in edge_to_tris.get(e, []):
149
+ boundary_region.add(neighbor)
150
+
151
+ region_indices = np.array(sorted(boundary_region), dtype=int)
152
+
153
+ # --- 对边界区域做法向量检查 ---
154
+ v0 = points[faces[region_indices, 0]]
155
+ v1 = points[faces[region_indices, 1]]
156
+ v2 = points[faces[region_indices, 2]]
157
+
158
+ normals = np.cross(v1 - v0, v2 - v0)
159
+ mag = np.linalg.norm(normals, axis=1, keepdims=True)
160
+ mag[mag == 0] = 1e-30
161
+ normals = normals / mag
162
+
163
+ region_keep = np.abs(normals[:, 2]) >= min_nz # |nz| < sin(θ) → 法向量接近水平 → 三角面接近垂直
164
+ keep_mask = np.ones(n_faces, dtype=bool)
165
+ keep_mask[region_indices] = region_keep
166
+
167
+ kept_faces = faces[keep_mask]
168
+
169
+ if len(kept_faces) == n_faces:
170
+ break # 本轮没有移除任何三角形,提前终止
171
+
172
+ faces = kept_faces
173
+
174
+ if len(faces) == 0:
175
+ return tin
176
+
177
+ # --- 顶点重新编号 ---
178
+ used_indices = np.unique(faces)
179
+ reindex_map = {old: new for new, old in enumerate(used_indices)}
180
+ reindexed_faces = np.array(
181
+ [[reindex_map[int(i)] for i in face] for face in faces],
182
+ dtype=int,
183
+ )
184
+ kept_points = points[used_indices]
185
+
186
+ return NativeTin(points=kept_points, faces=reindexed_faces)
187
+
188
+
72
189
  def _tingeometry_to_native_tin(tin: TINGeometry) -> NativeTin:
73
190
  """将 TINGeometry 转换为 NativeTin"""
74
191
  return NativeTin(
@@ -325,3 +442,76 @@ def multi_parallel_contour_modeling_impl(
325
442
  except Exception as exc:
326
443
  raise ParallelContourModelingError(f"多平行轮廓线建模失败: {exc}") from exc
327
444
  return [_native_tin_to_tingeometry(item) for item in result]
445
+
446
+
447
+ class SurfaceModeling:
448
+ """
449
+ 地表建模 — 从等高线生成三角网地表(DTM)
450
+
451
+ 推荐用法(无需实例化)
452
+ --------------------
453
+ >>> lines = [line1, line2, line3] # 等高线列表
454
+ >>> SurfaceModeling.create(lines, mode=0)
455
+ """
456
+
457
+ @staticmethod
458
+ def create(contour_lines, mode: int = 0, *,
459
+ vertical_angle_threshold: float = 25.0,
460
+ vertical_iterations: int = 10) -> List[TINGeometry]:
461
+ """
462
+ 地表建模:从等高线生成三角网地表
463
+
464
+ Parameters
465
+ ----------
466
+ contour_lines : List[Line] or List[np.ndarray] or List[List[List[float]]]
467
+ 等高线列表,每条为三维点序列。
468
+ mode : int
469
+ 建模模式,默认 0。
470
+ vertical_angle_threshold : float
471
+ 判定伪影三角形的法向量夹角阈值(度),默认 25.0。
472
+ vertical_iterations : int
473
+ 伪影剥离最大迭代次数,默认 10。某轮无新伪影则提前退出。
474
+
475
+ Returns
476
+ -------
477
+ List[TINGeometry]
478
+ 生成的三角网地表列表。
479
+ """
480
+ return surface_modeling_impl(
481
+ contour_lines, mode,
482
+ vertical_angle_threshold=vertical_angle_threshold,
483
+ vertical_iterations=vertical_iterations,
484
+ )
485
+
486
+
487
+ def surface_modeling_impl(contour_lines, mode: int = 0, *,
488
+ vertical_angle_threshold: float = 25.0,
489
+ vertical_iterations: int = 10) -> List[TINGeometry]:
490
+ """地表建模:输入等高线,输出三角网地表
491
+
492
+ Parameters
493
+ ----------
494
+ contour_lines : list
495
+ 等高线列表。
496
+ mode : int
497
+ 建模模式,默认 0。
498
+ vertical_angle_threshold : float
499
+ 判定伪影三角形的法向量夹角阈值(度),默认 25.0。
500
+ vertical_iterations : int
501
+ 伪影剥离最大迭代次数,默认 10。某轮无新伪影则提前退出。
502
+ """
503
+ normalized = [_normalize_line(line) for line in contour_lines]
504
+ try:
505
+ result = _native_surface_modeling(normalized, mode=mode)
506
+ except Exception as exc:
507
+ raise SurfaceModelingError(f"地表建模失败: {exc}") from exc
508
+
509
+ tins: List[TINGeometry] = []
510
+ for item in result:
511
+ item = _remove_vertical_triangles(
512
+ item,
513
+ angle_threshold=vertical_angle_threshold,
514
+ iterations=vertical_iterations,
515
+ )
516
+ tins.append(_native_tin_to_tingeometry(item))
517
+ return tins
@@ -261,3 +261,19 @@ def _normalize_polylines_set(data) -> List[List[List[float]]]:
261
261
  raise TypeError(
262
262
  f"不支持的 polylines_set 类型: {type(data)},支持 List[Line]、List[np.ndarray]、List[List[List[float]]]"
263
263
  )
264
+
265
+
266
+ class SurfaceModelingParam(ParamModel):
267
+ """
268
+ 地表建模参数
269
+
270
+ 示例
271
+ ------
272
+ >>> SurfaceModelingParam(mode=0)
273
+ """
274
+
275
+ mode: int = Field(
276
+ default=0,
277
+ ge=0,
278
+ description="建模模式,0=默认模式",
279
+ )
@@ -221,17 +221,14 @@ def _model_entity_to_native(entity: Any) -> NativeEntity:
221
221
  properties=props,
222
222
  )
223
223
  if isinstance(entity, TextInfo):
224
- text_props = [p for p in props if p.name != "text"]
225
- text_props.append(
226
- NativeProperty(name="text", type="string", value=entity.text)
227
- )
228
- return NativeEntity(
229
- entity_type="text",
230
- name=name,
231
- feature_name=feature_name,
232
- geometry=np.asarray(entity.position, dtype=float),
224
+ normal = getattr(entity, "normal", [0, 0, 1]) or [0, 0, 1]
225
+ return NativeText(
226
+ position=np.asarray(entity.position, dtype=float),
227
+ text=entity.text,
233
228
  color=color,
234
- properties=text_props,
229
+ height=entity.size,
230
+ normal=np.asarray(normal, dtype=float),
231
+ rotation=getattr(entity, "rotate", 0.0) or 0.0,
235
232
  )
236
233
  raise DmfInsertError(f"不支持的实体类型: {type(entity).__name__}")
237
234
 
@@ -271,6 +268,10 @@ def _model_layer_to_native(layer: DmfLayer) -> NativeLayer:
271
268
  entities = []
272
269
  for idx, entity in enumerate(layer.entities):
273
270
  ne = _model_entity_to_native(entity)
271
+ # NativeText 没有 name/feature_name 属性,跳过命名逻辑
272
+ if isinstance(ne, NativeText):
273
+ entities.append(ne)
274
+ continue
274
275
  # 兼容旧行为:未命名实体使用 "feature-index" 作为实体名称
275
276
  if not ne.name:
276
277
  ne.name = f"{ne.feature_name or '0'}-{idx}"
@@ -557,11 +558,12 @@ class DmfFile:
557
558
  *,
558
559
  name: str = "",
559
560
  color: Optional[List[int]] = None,
561
+ size: float = 20,
560
562
  properties: Optional[List[PropertyValue]] = None,
561
563
  ) -> None:
562
564
  """向指定图层插入文本标注"""
563
565
  _, lyr = self._resolve_layer(layer)
564
- lyr.insert_text(position, text, name=name, color=color)
566
+ lyr.insert_text(position, text, name=name, color=color, size=size)
565
567
  if properties:
566
568
  lyr.entities[-1].properties = list(properties)
567
569
 
@@ -300,7 +300,7 @@ class DmfLayer(LayerInfo):
300
300
  tin = TINGeometry(points=pts[:, :3], faces=fcs)
301
301
  self.entities.append(Shell(feature=name, geometry=tin, color=color))
302
302
 
303
- def insert_text(self, position: np.ndarray, text: str, *, name: str = "", color: Optional[List[int]] = None) -> None:
303
+ def insert_text(self, position: np.ndarray, text: str, *, name: str = "", color: Optional[List[int]] = None, size: float = 20) -> None:
304
304
  """向本图层插入文本标注"""
305
305
  from dimine_python_sdk.lib.io.dmf_file import DmfInsertError
306
306
 
@@ -308,4 +308,4 @@ class DmfLayer(LayerInfo):
308
308
  if pos.shape[0] < 3:
309
309
  raise DmfInsertError(f"文本位置至少需要3个分量,当前: {pos.shape[0]}")
310
310
 
311
- self.entities.append(TextInfo(feature=name, text=text, position=pos[:3].tolist(), color=color or [255, 255, 255]))
311
+ self.entities.append(TextInfo(feature=name, text=text, position=pos[:3].tolist(), color=color or [255, 255, 255], size=size))
@@ -82,6 +82,7 @@ from dimine_python_sdk.lib.native.exploitation import (
82
82
  laneway_modeling,
83
83
  multi_parallel_contour_modeling,
84
84
  single_contour_modeling,
85
+ surface_modeling,
85
86
  two_contour_modeling,
86
87
  unite_polydata,
87
88
  )
@@ -161,4 +162,5 @@ __all__ = [
161
162
  "contour_extrapolate_modeling",
162
163
  "multi_parallel_contour_modeling",
163
164
  "unite_polydata",
165
+ "surface_modeling",
164
166
  ]
@@ -11,6 +11,7 @@ from typing import Any
11
11
  from dimine_python_sdk.lib.native._core import Dm
12
12
  from dimine_python_sdk.lib.native._base import (
13
13
  NativeExploitationError,
14
+ ensure_points,
14
15
  parse_json_response,
15
16
  )
16
17
  from dimine_python_sdk.lib.native.models import NativeLithologyModel, NativeTin
@@ -159,6 +160,66 @@ def implicit_stratum_modeling(param: dict | str) -> list[NativeLithologyModel]:
159
160
  ]
160
161
 
161
162
 
163
+ def surface_modeling(contour_lines: list, mode: int = 0) -> list[NativeTin]:
164
+ """
165
+ 地表建模:从等高线生成三角网地表(DTM)。
166
+
167
+ 将输入的等高线写入临时 DMF 文件,调用 C++ SurfaceModeling 生成地表
168
+ 三角网,读取结果后清理临时文件。
169
+
170
+ Parameters
171
+ ----------
172
+ contour_lines : list of np.ndarray
173
+ 等高线列表,每条为 shape (N, 3) 的 numpy 数组。
174
+ mode : int
175
+ 建模模式,默认 0。
176
+
177
+ Returns
178
+ -------
179
+ list[NativeTin]
180
+ 生成的三角网地表列表。
181
+ """
182
+ import json
183
+ import os
184
+ import shutil
185
+ import tempfile
186
+
187
+ import numpy as np
188
+
189
+ from dimine_python_sdk.lib.native.io import write_dmf
190
+ from dimine_python_sdk.lib.native.models import NativeEntity, NativeLayer
191
+
192
+ # 构建 NativeLayer,每条等高线为一个 polyline 实体
193
+ entities: list[NativeEntity] = []
194
+ for i, line in enumerate(contour_lines):
195
+ pts = np.asarray(line, dtype=float)
196
+ if pts.ndim != 2 or pts.shape[1] < 2:
197
+ raise NativeExploitationError(
198
+ f"等高线 {i} 格式无效: shape={pts.shape},期望 (N, 3)"
199
+ )
200
+ entities.append(
201
+ NativeEntity(
202
+ entity_type="polyline",
203
+ name=f"contour_{i}",
204
+ geometry=ensure_points(pts),
205
+ )
206
+ )
207
+ layer = NativeLayer(name="contours", entities=entities)
208
+
209
+ tmp_dir = tempfile.mkdtemp(prefix="dm_surface_")
210
+ tmp_file = os.path.join(tmp_dir, "surface_input.dmf")
211
+ try:
212
+ write_dmf(tmp_file, [layer])
213
+ param = json.dumps({"file": tmp_file, "mode": int(mode)}, ensure_ascii=False)
214
+ try:
215
+ result = Dm.SurfaceModeling(param)
216
+ except Exception as exc:
217
+ raise NativeExploitationError(f"地表建模失败: {exc}") from exc
218
+ return _convert_polydata_list(result, "地表建模")
219
+ finally:
220
+ shutil.rmtree(tmp_dir, ignore_errors=True)
221
+
222
+
162
223
  __all__ = [
163
224
  "get_section_contour",
164
225
  "laneway_modeling",
@@ -168,4 +229,5 @@ __all__ = [
168
229
  "multi_parallel_contour_modeling",
169
230
  "unite_polydata",
170
231
  "implicit_stratum_modeling",
232
+ "surface_modeling",
171
233
  ]