stbkit 0.1.0a9.dev1__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 (125) hide show
  1. stbkit/__main__.py +10 -0
  2. stbkit/api/__init__.py +48 -0
  3. stbkit/api/experimental/__init__.py +19 -0
  4. stbkit/api/experimental/repository/__init__.py +30 -0
  5. stbkit/api/py.typed +0 -0
  6. stbkit/api/stb_latest.py +7 -0
  7. stbkit/api/stb_v2_0_2.py +11 -0
  8. stbkit/api/stb_v2_1_1.py +9 -0
  9. stbkit/cli/__init__.py +17 -0
  10. stbkit/cli/_internal/cli_main.py +114 -0
  11. stbkit/cli/_internal/common.py +126 -0
  12. stbkit/cli/_internal/constants_exit_code.py +59 -0
  13. stbkit/cli/_internal/converter.py +693 -0
  14. stbkit/cli/_internal/diff.py +213 -0
  15. stbkit/cli/_internal/entry_points.py +25 -0
  16. stbkit/cli/_internal/global_parent.py +70 -0
  17. stbkit/cli/_internal/show_versions.py +36 -0
  18. stbkit/cli/_internal/validate.py +310 -0
  19. stbkit/cli/commands/convert.py +16 -0
  20. stbkit/cli/commands/diff.py +16 -0
  21. stbkit/cli/commands/validate.py +16 -0
  22. stbkit/cli/py.typed +0 -0
  23. stbkit/tools/__init__.py +12 -0
  24. stbkit/tools/_internal/__init__.py +5 -0
  25. stbkit/tools/_internal/constants.py +21 -0
  26. stbkit/tools/_internal/data_model/building_geometry.py +1208 -0
  27. stbkit/tools/_internal/data_model/building_geometry_web.py +106 -0
  28. stbkit/tools/_internal/data_model/element_data.py +93 -0
  29. stbkit/tools/_internal/data_model/ifc_data/entity_data.py +67 -0
  30. stbkit/tools/_internal/data_model/ifc_data/entity_names.py +32 -0
  31. stbkit/tools/_internal/data_model/shape_data.py +1089 -0
  32. stbkit/tools/_internal/diff/__init__.py +5 -0
  33. stbkit/tools/_internal/diff/common.py +50 -0
  34. stbkit/tools/_internal/diff/diff_xml.py +796 -0
  35. stbkit/tools/_internal/matrix.py +51 -0
  36. stbkit/tools/_internal/optional_dependencies/__init__.py +5 -0
  37. stbkit/tools/_internal/optional_dependencies/ifc_common.py +28 -0
  38. stbkit/tools/_internal/optional_dependencies/ifc_exporter/ifc_entity_exporter.py +473 -0
  39. stbkit/tools/_internal/optional_dependencies/ifc_exporter/ifc_profile_exporter.py +267 -0
  40. stbkit/tools/_internal/optional_dependencies/ifc_exporter/ifc_representation_exporter.py +178 -0
  41. stbkit/tools/_internal/optional_dependencies/ifc_pipeline.py +133 -0
  42. stbkit/tools/_internal/optional_dependencies/ifc_to_building_geometry.py +64 -0
  43. stbkit/tools/_internal/stb_io.py +116 -0
  44. stbkit/tools/_internal/stb_repair/_common.py +24 -0
  45. stbkit/tools/_internal/stb_repair/_stb_repair.py +197 -0
  46. stbkit/tools/_internal/stb_repair/_stb_v2_0_2_repair.py +53 -0
  47. stbkit/tools/_internal/stb_repair/_stb_v2_1_0_repair.py +109 -0
  48. stbkit/tools/_internal/stb_repair/_stb_v2_1_1_repair.py +109 -0
  49. stbkit/tools/_internal/studio_report.py +49 -0
  50. stbkit/tools/_internal/utils/atomic_writer.py +57 -0
  51. stbkit/tools/_internal/utils/float_rounding.py +49 -0
  52. stbkit/tools/_internal/utils/girder_utils.py +91 -0
  53. stbkit/tools/_internal/utils/guid_utils.py +30 -0
  54. stbkit/tools/_internal/utils/mesh_utils.py +1305 -0
  55. stbkit/tools/_internal/utils/node_utils.py +50 -0
  56. stbkit/tools/_internal/utils/unit_utils.py +23 -0
  57. stbkit/tools/_internal/vectors.py +222 -0
  58. stbkit/tools/converters/__init__.py +39 -0
  59. stbkit/tools/converters/_internal/__init__.py +5 -0
  60. stbkit/tools/converters/_internal/building_geometry_to_ply_text.py +32 -0
  61. stbkit/tools/converters/_internal/coord_converter.py +151 -0
  62. stbkit/tools/converters/_internal/element_data_to_building_geometry.py +549 -0
  63. stbkit/tools/converters/_internal/ifc_to_ply_text.py +21 -0
  64. stbkit/tools/converters/_internal/name_converter.py +23 -0
  65. stbkit/tools/converters/_internal/pipeline/__init__.py +5 -0
  66. stbkit/tools/converters/_internal/pipeline/data.py +188 -0
  67. stbkit/tools/converters/_internal/pipeline/execution.py +569 -0
  68. stbkit/tools/converters/_internal/pipeline/ply_pipeline.py +52 -0
  69. stbkit/tools/converters/_internal/pipeline/stb_pipeline.py +226 -0
  70. stbkit/tools/converters/_internal/pipeline/stb_pipeline_latest.py +39 -0
  71. stbkit/tools/converters/_internal/pipeline/step.py +139 -0
  72. stbkit/tools/converters/_internal/stb_latest_to_element_data/__init__.py +17 -0
  73. stbkit/tools/converters/_internal/stb_latest_to_element_data/_stb_latest_to_element_data.py +186 -0
  74. stbkit/tools/converters/_internal/stb_latest_to_element_data/member/member_footing.py +49 -0
  75. stbkit/tools/converters/_internal/stb_latest_to_element_data/member/member_line.py +533 -0
  76. stbkit/tools/converters/_internal/stb_latest_to_element_data/member/member_pile.py +100 -0
  77. stbkit/tools/converters/_internal/stb_latest_to_element_data/member/member_plane.py +103 -0
  78. stbkit/tools/converters/_internal/stb_latest_to_element_data/open.py +70 -0
  79. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec.py +214 -0
  80. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_beam_rc.py +67 -0
  81. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_beam_s.py +97 -0
  82. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_beam_src.py +28 -0
  83. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_brace_s.py +49 -0
  84. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_column_cft.py +217 -0
  85. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_column_rc.py +41 -0
  86. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_column_s.py +127 -0
  87. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_column_src.py +173 -0
  88. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_foundation.py +225 -0
  89. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_parapet_rc.py +53 -0
  90. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_pile_rc.py +106 -0
  91. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_pile_s.py +100 -0
  92. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_steel.py +432 -0
  93. stbkit/tools/converters/_internal/stb_to_building_geometry.py +44 -0
  94. stbkit/tools/converters/_internal/stb_to_ifc_data/__init__.py +5 -0
  95. stbkit/tools/converters/_internal/stb_to_ifc_data/_stb_to_ifc_data.py +59 -0
  96. stbkit/tools/converters/_internal/stb_to_ifc_data/convert_option.py +22 -0
  97. stbkit/tools/converters/_internal/stb_to_ply_text.py +22 -0
  98. stbkit/tools/py.typed +0 -0
  99. stbkit/tools/upgrade/__init__.py +13 -0
  100. stbkit/tools/upgrade/_internal/__init__.py +5 -0
  101. stbkit/tools/upgrade/_internal/common.py +205 -0
  102. stbkit/tools/upgrade/_internal/upgrade.py +228 -0
  103. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/__init__.py +60 -0
  104. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/hook_common.py +62 -0
  105. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/hook_joint_beam_shape_h.py +44 -0
  106. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/hook_sec_pile_rc.py +101 -0
  107. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/hook_sec_pile_s.py +78 -0
  108. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/hook_sec_slab_deck.py +56 -0
  109. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/hook_sec_slab_rc.py +128 -0
  110. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/post_girder.py +126 -0
  111. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/post_open.py +110 -0
  112. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/post_sec_beam_rc.py +227 -0
  113. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/post_sec_beam_s.py +1153 -0
  114. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hooks.py +22 -0
  115. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/post.py +42 -0
  116. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/tables.py +99 -0
  117. stbkit/tools/upgrade/_internal/v2_1_0_to_v2_1_1/__init__.py +58 -0
  118. stbkit/tools/upgrade/_internal/v2_1_0_to_v2_1_1/hooks.py +32 -0
  119. stbkit/tools/upgrade/_internal/v2_1_0_to_v2_1_1/tables.py +56 -0
  120. stbkit-0.1.0a9.dev1.dist-info/METADATA +196 -0
  121. stbkit-0.1.0a9.dev1.dist-info/RECORD +125 -0
  122. stbkit-0.1.0a9.dev1.dist-info/WHEEL +5 -0
  123. stbkit-0.1.0a9.dev1.dist-info/entry_points.txt +2 -0
  124. stbkit-0.1.0a9.dev1.dist-info/licenses/LICENSE +373 -0
  125. stbkit-0.1.0a9.dev1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,569 @@
1
+ # Copyright 2026 TAISEI CORPORATION
2
+ # SPDX-License-Identifier: MPL-2.0
3
+ # This Source Code Form is subject to the terms of the Mozilla Public
4
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
5
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
+
7
+ import hashlib
8
+ import itertools
9
+ import os
10
+ import re
11
+ import shutil
12
+ from collections.abc import Callable, Sequence
13
+ from enum import StrEnum
14
+ from pathlib import Path
15
+ from typing import Any
16
+ from uuid import uuid4
17
+
18
+ from stbkit.core import stb_io
19
+ from stbkit.core.data_model.common import StBridgeRoot
20
+ from stbkit.core.stb_exceptions import SchemaError
21
+ from stbkit.core.stb_io._internal.serializer import _raw_dumps
22
+ from stbkit.core.stb_reporting import Reporter, get_reporter
23
+
24
+ from stbkit.tools._internal.constants import DEBUG_DUMP_DIR_ENV
25
+
26
+ from ...._internal.utils.atomic_writer import (
27
+ write_text_stream_with_temp_file,
28
+ write_text_with_temp_file,
29
+ write_with_temp_file,
30
+ )
31
+ from ..building_geometry_to_ply_text import building_geometry_to_ply_text
32
+ from .data import (
33
+ _Data,
34
+ _DataFormat,
35
+ _DataFormatSet,
36
+ _DataKind,
37
+ _FileData,
38
+ _StbVersion,
39
+ _TextData,
40
+ )
41
+ from .ply_pipeline import _PlyData, _StbToPlyStep, save_ply_data
42
+ from .stb_pipeline import (
43
+ StbAssignGuidsStep,
44
+ StbData,
45
+ StbDumpsStep,
46
+ StbFileData,
47
+ StbLoadStep,
48
+ StbRepairStep,
49
+ StbTextData,
50
+ StbToVersionStep,
51
+ )
52
+ from .stb_pipeline_latest import StbToLatestStep
53
+ from .step import (
54
+ _compose_steps,
55
+ _ExecutableStep,
56
+ _execute_step,
57
+ _ExecutionContext,
58
+ _ExecutionResult,
59
+ _NoOpStep,
60
+ )
61
+
62
+
63
+ class ConvertResultFormat(StrEnum):
64
+ FILE = "file"
65
+ TEXT = "text"
66
+ FILE_OR_TEXT = "file_or_text"
67
+
68
+
69
+ class _UnsupportedConversionError(NotImplementedError):
70
+ """パイプラインに変換ルートが登録されていない場合の例外"""
71
+
72
+
73
+ def _short_name_for_dump(name: str, *, max_length: int = 64) -> str:
74
+ cleaned: str = re.sub(r"[^A-Za-z0-9._-]+", "_", name).strip("._-") or "unknown"
75
+ if len(cleaned) <= max_length:
76
+ return cleaned
77
+ hash: str = hashlib.sha1(cleaned.encode("utf-8")).hexdigest()[:12]
78
+ length = max_length - (len(hash) + 1)
79
+ if length <= 0:
80
+ return hash
81
+ return f"{cleaned[:length]}_{hash}"
82
+
83
+
84
+ def _debug_dump_path_for_test(data: StbData[Any]) -> Path | None:
85
+ debug_dump_dir = os.getenv(DEBUG_DUMP_DIR_ENV)
86
+ if not debug_dump_dir:
87
+ return None
88
+
89
+ test_name: str = os.getenv("PYTEST_CURRENT_TEST", "unknown_test")
90
+ node_id: str = test_name.split(" ", maxsplit=1)[0]
91
+ short_test_name: str = _short_name_for_dump(node_id.rsplit("::", maxsplit=1)[-1])
92
+ stb_version: _StbVersion = data.stb_version or _StbVersion.UNSPECIFIED
93
+ model_name: str = _short_name_for_dump(f"object_stb_{stb_version.value}")
94
+ file_name: str = f"{model_name}_{short_test_name}_{uuid4().hex}_error.stb"
95
+ return Path(debug_dump_dir).expanduser() / file_name
96
+
97
+
98
+ def _normalize_format_set(
99
+ data_format: _DataFormat | _DataFormatSet,
100
+ *,
101
+ kind: _DataKind,
102
+ stb_version: _StbVersion | None = None,
103
+ ) -> _DataFormatSet:
104
+ if isinstance(data_format, _DataFormatSet):
105
+ return data_format
106
+ return _DataFormatSet(
107
+ format=data_format,
108
+ kind=kind,
109
+ stb_version=stb_version if data_format == _DataFormat.STB else None,
110
+ )
111
+
112
+
113
+ def to_text(
114
+ data: _Data,
115
+ reporter: Reporter,
116
+ ) -> str:
117
+ if isinstance(data, _TextData):
118
+ return data.text
119
+ if isinstance(data, _FileData):
120
+ with open(data.path, encoding=data.encoding) as f:
121
+ return f.read()
122
+ if data.format == _DataFormat.STB and isinstance(data, StbData):
123
+ try:
124
+ return stb_io.dumps(data.value, reporter=reporter)
125
+ except SchemaError:
126
+ debug_dump_path = _debug_dump_path_for_test(data)
127
+ if debug_dump_path is not None:
128
+ stb_error: str = _raw_dumps(
129
+ data.value, reporter=reporter, _strict=False
130
+ )
131
+ try:
132
+ debug_dump_path.parent.mkdir(parents=True, exist_ok=True)
133
+ write_text_with_temp_file(
134
+ debug_dump_path,
135
+ stb_error,
136
+ encoding="utf-8",
137
+ )
138
+ except OSError as error:
139
+ reporter.warning(
140
+ "デバッグダンプの保存に失敗しました: "
141
+ f"path={debug_dump_path} error={error}"
142
+ )
143
+ raise
144
+ if data.format == _DataFormat.PLY and isinstance(data, _PlyData):
145
+ return building_geometry_to_ply_text(data.value)
146
+ if data.format == _DataFormat.IFC:
147
+ from ...._internal.optional_dependencies import ifc_pipeline
148
+
149
+ if isinstance(data, ifc_pipeline.IfcData):
150
+ return data.value.to_string()
151
+ raise ValueError(f"テキスト化できないデータ形式です: {type(data)}")
152
+
153
+
154
+ def save_data(
155
+ data: _Data,
156
+ path: Path,
157
+ reporter: Reporter,
158
+ encoding: str = "utf-8",
159
+ ) -> None:
160
+ path.parent.mkdir(parents=True, exist_ok=True)
161
+ if isinstance(data, _FileData):
162
+ if data.path.absolute() != path.absolute():
163
+
164
+ def _copy_to_temp(temporary_path: Path) -> None:
165
+ shutil.copyfile(data.path, temporary_path)
166
+
167
+ write_with_temp_file(path, _copy_to_temp)
168
+ return
169
+ if isinstance(data, _TextData):
170
+ write_text_with_temp_file(path, data.text, encoding=encoding)
171
+ return
172
+ if data.format == _DataFormat.STB and isinstance(data, StbData):
173
+ write_text_stream_with_temp_file(
174
+ path,
175
+ lambda stream: stb_io.dump(data.value, stream, reporter=reporter),
176
+ encoding=encoding,
177
+ )
178
+ return
179
+ if data.format == _DataFormat.IFC:
180
+ from ...._internal.optional_dependencies import ifc_pipeline
181
+
182
+ if isinstance(data, ifc_pipeline.IfcData):
183
+ write_with_temp_file(
184
+ path, lambda temporary_path: data.value.write(temporary_path)
185
+ )
186
+ return
187
+ if data.format == _DataFormat.PLY and isinstance(data, _PlyData):
188
+ write_with_temp_file(
189
+ path,
190
+ lambda temporary_path: save_ply_data(data, temporary_path),
191
+ )
192
+ return
193
+ raise ValueError(f"保存できないデータ形式です: {type(data)}")
194
+
195
+
196
+ def open_data(
197
+ path: Path,
198
+ data_format: _DataFormat | _DataFormatSet,
199
+ reporter: Reporter,
200
+ *,
201
+ stb_version: _StbVersion | None = None,
202
+ encoding: str | None = None,
203
+ enable_repair: bool = False,
204
+ max_size: int | None = None,
205
+ ) -> _Data:
206
+ format_set = _normalize_format_set(
207
+ data_format,
208
+ kind=_DataKind.OBJECT,
209
+ stb_version=stb_version,
210
+ )
211
+ match format_set.format:
212
+ case _DataFormat.STB:
213
+ stb_file: StbFileData = StbFileData(
214
+ path,
215
+ stb_version=format_set.stb_version or _StbVersion.UNSPECIFIED,
216
+ encoding=encoding,
217
+ )
218
+ result = _execute_step(
219
+ StbLoadStep(),
220
+ stb_file,
221
+ context=_ExecutionContext(reporter=reporter, max_size=max_size),
222
+ )
223
+ data: StbData[StBridgeRoot] = result.output
224
+ if enable_repair:
225
+ repaired = _execute_step(
226
+ StbRepairStep[StBridgeRoot](),
227
+ data,
228
+ context=_ExecutionContext(reporter=reporter),
229
+ )
230
+ data = repaired.output
231
+ return data
232
+ case _DataFormat.IFC:
233
+ from ...._internal.optional_dependencies import ifc_pipeline
234
+
235
+ return ifc_pipeline.open_ifc_data(path)
236
+ case _:
237
+ raise _UnsupportedConversionError(
238
+ f"読み込みできないデータ形式です: {format_set.format}"
239
+ )
240
+
241
+
242
+ def open_text_data(
243
+ text: str,
244
+ data_format: _DataFormat | _DataFormatSet,
245
+ reporter: Reporter,
246
+ *,
247
+ stb_version: _StbVersion | None = None,
248
+ enable_repair: bool = False,
249
+ max_size: int | None = None,
250
+ ) -> _Data:
251
+ format_set = _normalize_format_set(
252
+ data_format,
253
+ kind=_DataKind.OBJECT,
254
+ stb_version=stb_version,
255
+ )
256
+ if format_set.format == _DataFormat.STB:
257
+ stb_text: StbTextData = StbTextData(
258
+ text,
259
+ stb_version=format_set.stb_version or _StbVersion.UNSPECIFIED,
260
+ )
261
+ result = _execute_step(
262
+ StbLoadStep(),
263
+ stb_text,
264
+ context=_ExecutionContext(reporter=reporter, max_size=max_size),
265
+ )
266
+ data: StbData[StBridgeRoot] = result.output
267
+ if enable_repair:
268
+ repaired = _execute_step(
269
+ StbRepairStep[StBridgeRoot](),
270
+ data,
271
+ context=_ExecutionContext(reporter=reporter),
272
+ )
273
+ data = repaired.output
274
+ return data
275
+ if format_set.format == _DataFormat.IFC:
276
+ from ...._internal.optional_dependencies import ifc_pipeline
277
+
278
+ return ifc_pipeline.loads_ifc_data(text)
279
+ raise _UnsupportedConversionError(
280
+ f"テキストから読み込みできないデータ形式です: {format_set.format}"
281
+ )
282
+
283
+
284
+ def _get_direct_step(
285
+ input_format: _DataFormatSet,
286
+ output_format: _DataFormatSet,
287
+ *,
288
+ assign_guids: bool = False,
289
+ ) -> _ExecutableStep[Any, Any]:
290
+ """中間形式を含まない1区間分の変換ステップ"""
291
+ if (
292
+ input_format.format == _DataFormat.STB
293
+ and output_format.format == _DataFormat.STB
294
+ ):
295
+ if input_format.kind in (_DataKind.FILE, _DataKind.TEXT) and (
296
+ output_format.kind == _DataKind.OBJECT
297
+ ):
298
+ loaded_format = _DataFormatSet(
299
+ _DataFormat.STB,
300
+ _DataKind.OBJECT,
301
+ stb_version=_StbVersion.UNSPECIFIED,
302
+ )
303
+ return _compose_steps(
304
+ f"{input_format} -> {output_format}",
305
+ (
306
+ StbLoadStep(),
307
+ _get_direct_step(
308
+ loaded_format,
309
+ output_format,
310
+ assign_guids=assign_guids,
311
+ ),
312
+ ),
313
+ )
314
+
315
+ if input_format.kind == _DataKind.OBJECT and (
316
+ output_format.kind == _DataKind.TEXT
317
+ ):
318
+ object_output = _DataFormatSet(
319
+ _DataFormat.STB,
320
+ _DataKind.OBJECT,
321
+ stb_version=output_format.stb_version,
322
+ )
323
+ return _compose_steps(
324
+ f"{input_format} -> {output_format}",
325
+ (
326
+ _get_direct_step(
327
+ input_format,
328
+ object_output,
329
+ assign_guids=assign_guids,
330
+ ),
331
+ StbDumpsStep(),
332
+ ),
333
+ )
334
+
335
+ if (
336
+ input_format.kind == _DataKind.OBJECT
337
+ and output_format.kind == _DataKind.OBJECT
338
+ ):
339
+ steps: list[_ExecutableStep[Any, Any]] = []
340
+ output_version = output_format.stb_version or _StbVersion.UNSPECIFIED
341
+ if output_version == _StbVersion.LATEST:
342
+ if input_format.stb_version != _StbVersion.LATEST:
343
+ steps.append(StbToLatestStep())
344
+ elif output_version != _StbVersion.UNSPECIFIED and (
345
+ input_format.stb_version != output_version
346
+ ):
347
+ steps.append(StbToVersionStep(output_version))
348
+ if assign_guids:
349
+ steps.append(StbAssignGuidsStep())
350
+ return _compose_steps(
351
+ f"{input_format} -> {output_format}",
352
+ steps,
353
+ )
354
+
355
+ if input_format == output_format:
356
+ return _NoOpStep()
357
+
358
+ if (
359
+ input_format.format == _DataFormat.STB
360
+ and output_format.format == _DataFormat.IFC
361
+ and input_format.kind == _DataKind.OBJECT
362
+ and output_format.kind == _DataKind.OBJECT
363
+ ):
364
+ from ...._internal.optional_dependencies import ifc_pipeline
365
+
366
+ steps = []
367
+ if input_format.stb_version != _StbVersion.LATEST:
368
+ steps.append(StbToLatestStep())
369
+ if assign_guids:
370
+ steps.append(StbAssignGuidsStep())
371
+ steps.append(ifc_pipeline.stb_latest_to_ifc_step())
372
+ return _compose_steps("STB to IFC", steps)
373
+
374
+ if (
375
+ input_format.format == _DataFormat.IFC
376
+ and output_format.format == _DataFormat.PLY
377
+ and input_format.kind == _DataKind.OBJECT
378
+ and output_format.kind == _DataKind.OBJECT
379
+ ):
380
+ from ...._internal.optional_dependencies import ifc_pipeline
381
+
382
+ return ifc_pipeline.IfcDataToPlyStep()
383
+
384
+ if (
385
+ input_format.format == _DataFormat.STB
386
+ and output_format.format == _DataFormat.PLY
387
+ and input_format.kind == _DataKind.OBJECT
388
+ and output_format.kind == _DataKind.OBJECT
389
+ ):
390
+ steps = []
391
+ if input_format.stb_version != _StbVersion.LATEST:
392
+ steps.append(StbToLatestStep())
393
+ if assign_guids:
394
+ steps.append(StbAssignGuidsStep())
395
+ steps.append(_StbToPlyStep())
396
+ return _compose_steps("STB to PLY", steps)
397
+
398
+ raise _UnsupportedConversionError(
399
+ f"変換ルートが未実装です: {input_format} -> {output_format}"
400
+ )
401
+
402
+
403
+ def get_step(
404
+ input_format: _DataFormatSet,
405
+ output_format: _DataFormatSet,
406
+ *,
407
+ route_formats: Sequence[_DataFormatSet] | None = None,
408
+ assign_guids: bool = False,
409
+ ) -> _ExecutableStep[Any, Any]:
410
+ """入力・中間・出力形式から実行可能な変換ステップを組み立てる。"""
411
+ if not route_formats:
412
+ return _get_direct_step(
413
+ input_format,
414
+ output_format,
415
+ assign_guids=assign_guids,
416
+ )
417
+
418
+ waypoints = [input_format, *route_formats, output_format]
419
+ steps: list[_ExecutableStep[Any, Any]] = []
420
+ guid_pending = assign_guids
421
+ last_index = len(waypoints) - 2
422
+ for index, (source, target) in enumerate(itertools.pairwise(waypoints)):
423
+ apply_guid = bool(
424
+ guid_pending
425
+ and source.format == _DataFormat.STB
426
+ and (target.format != _DataFormat.STB or index == last_index)
427
+ )
428
+ steps.append(
429
+ _get_direct_step(
430
+ source,
431
+ target,
432
+ assign_guids=apply_guid,
433
+ )
434
+ )
435
+ if apply_guid:
436
+ guid_pending = False
437
+
438
+ return _compose_steps(
439
+ f"{input_format} -> {output_format}",
440
+ steps,
441
+ )
442
+
443
+
444
+ def execute_conversion(
445
+ input_data: _Data,
446
+ output_format: _DataFormatSet,
447
+ *,
448
+ route_formats: Sequence[_DataFormatSet] | None = None,
449
+ reporter: Reporter | None = None,
450
+ assign_guids: bool = False,
451
+ output_enable_repair: bool = False,
452
+ on_step_completed: Callable[[str, _Data], None] | None = None,
453
+ work_dir: Path | None = None,
454
+ keep_temporary_files: bool = False,
455
+ ifc_round_digits: int | None = None,
456
+ ifc_round_digits_mm: int | None = None,
457
+ ) -> _ExecutionResult[_Data]:
458
+ reporter = reporter or get_reporter()
459
+ step = get_step(
460
+ input_data.format_set,
461
+ output_format,
462
+ route_formats=route_formats,
463
+ assign_guids=assign_guids,
464
+ )
465
+ if output_enable_repair and output_format.format == _DataFormat.STB:
466
+ step = _compose_steps(
467
+ "Conversion with Repair",
468
+ (step, StbRepairStep()),
469
+ )
470
+ context = _ExecutionContext(
471
+ work_dir=work_dir,
472
+ keep_temporary_files=keep_temporary_files,
473
+ reporter=reporter,
474
+ on_step_completed=on_step_completed,
475
+ ifc_round_digits=ifc_round_digits,
476
+ ifc_round_digits_mm=ifc_round_digits_mm,
477
+ )
478
+ return _execute_step(step, input_data, context=context)
479
+
480
+
481
+ def cleanup_paths(paths: Sequence[Path]) -> None:
482
+ """パイプラインが生成した一時ファイル・ディレクトリを削除する"""
483
+ for path in reversed(tuple(dict.fromkeys(paths))):
484
+ if path.is_dir():
485
+ shutil.rmtree(path, ignore_errors=True)
486
+ else:
487
+ path.unlink(missing_ok=True)
488
+
489
+
490
+ def convert(
491
+ input_path: Path,
492
+ input_format: _DataFormat,
493
+ output_format: _DataFormat,
494
+ result_format: ConvertResultFormat,
495
+ *,
496
+ input_stb_version: _StbVersion = _StbVersion.UNSPECIFIED,
497
+ output_stb_version: _StbVersion | None = None,
498
+ route_formats: Sequence[_DataFormat | _DataFormatSet] | None = None,
499
+ output_path: Path | None = None,
500
+ reporter: Reporter | None = None,
501
+ input_encoding: str | None = None,
502
+ input_enable_repair: bool = False,
503
+ output_enable_repair: bool = False,
504
+ assign_guids: bool = False,
505
+ keep_temporary_files: bool = False,
506
+ ifc_round_digits: int | None = None,
507
+ ifc_round_digits_mm: int | None = None,
508
+ ) -> str | Path:
509
+ reporter = reporter or get_reporter()
510
+ if output_format == _DataFormat.STB:
511
+ output_stb_version = output_stb_version or _StbVersion.LATEST
512
+ else:
513
+ output_stb_version = None
514
+
515
+ input_format_set = _DataFormatSet(
516
+ input_format,
517
+ _DataKind.OBJECT,
518
+ stb_version=input_stb_version if input_format == _DataFormat.STB else None,
519
+ )
520
+ output_format_set = _DataFormatSet(
521
+ output_format,
522
+ _DataKind.OBJECT,
523
+ stb_version=output_stb_version if output_format == _DataFormat.STB else None,
524
+ )
525
+ normalized_routes = [
526
+ route
527
+ if isinstance(route, _DataFormatSet)
528
+ else _DataFormatSet(route, _DataKind.OBJECT)
529
+ for route in (route_formats or ())
530
+ ]
531
+
532
+ input_data = open_data(
533
+ input_path,
534
+ input_format_set,
535
+ reporter,
536
+ encoding=input_encoding,
537
+ enable_repair=input_enable_repair,
538
+ )
539
+ result = execute_conversion(
540
+ input_data,
541
+ output_format_set,
542
+ route_formats=normalized_routes,
543
+ reporter=reporter,
544
+ assign_guids=assign_guids,
545
+ output_enable_repair=output_enable_repair,
546
+ keep_temporary_files=keep_temporary_files,
547
+ ifc_round_digits=ifc_round_digits,
548
+ ifc_round_digits_mm=ifc_round_digits_mm,
549
+ )
550
+ try:
551
+ if result_format in (
552
+ ConvertResultFormat.TEXT,
553
+ ConvertResultFormat.FILE_OR_TEXT,
554
+ ):
555
+ return to_text(
556
+ result.output,
557
+ reporter,
558
+ )
559
+ if result_format == ConvertResultFormat.FILE:
560
+ if output_path is None:
561
+ raise ValueError(
562
+ "output_path must be specified when result_format is FILE"
563
+ )
564
+ save_data(result.output, output_path, reporter)
565
+ return output_path
566
+ raise NotImplementedError(f"未実装の結果形式です: {result_format}")
567
+ finally:
568
+ if not keep_temporary_files:
569
+ cleanup_paths(result.cleanup_paths)
@@ -0,0 +1,52 @@
1
+ # Copyright 2026 TAISEI CORPORATION
2
+ # SPDX-License-Identifier: MPL-2.0
3
+ # This Source Code Form is subject to the terms of the Mozilla Public
4
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
5
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
+
7
+ from pathlib import Path
8
+
9
+ from stbkit.core.stb_reporting import NullReporter
10
+
11
+ from ...._internal.data_model.building_geometry import BuildingGeometry
12
+ from ..building_geometry_to_ply_text import building_geometry_to_ply_text
13
+ from ..element_data_to_building_geometry import element_data_to_building_geometry
14
+ from ..stb_latest_to_element_data import stb_to_element_data
15
+ from .data import _DataFormat, _ObjectData
16
+ from .stb_pipeline_latest import StbLatestData
17
+ from .step import _ExecutableStep, _ExecutionContext, _ExecutionResult
18
+
19
+
20
+ class _PlyData(_ObjectData[BuildingGeometry]):
21
+ def __init__(self, value: BuildingGeometry) -> None:
22
+ super().__init__(format=_DataFormat.PLY, value=value)
23
+
24
+
25
+ def save_ply_data(data: _PlyData, path: Path) -> None:
26
+ ply_text: str = building_geometry_to_ply_text(data.value)
27
+ with open(path, "w", encoding="utf-8") as f:
28
+ f.write(ply_text)
29
+
30
+
31
+ class _StbToPlyStep(_ExecutableStep[StbLatestData, _PlyData]):
32
+ name = "STB -> PLY"
33
+
34
+ def execute(
35
+ self, input: StbLatestData, *, context: _ExecutionContext
36
+ ) -> _ExecutionResult[_PlyData]:
37
+ if not isinstance(input, StbLatestData):
38
+ raise TypeError(f"StBridgeデータではありません: {type(input)}")
39
+ elements = stb_to_element_data(
40
+ input.value,
41
+ reporter=context.reporter
42
+ if context.reporter is not None
43
+ else NullReporter(),
44
+ )
45
+ mesh_data = element_data_to_building_geometry(
46
+ elements,
47
+ allow_polygons=True,
48
+ reporter=context.reporter
49
+ if context.reporter is not None
50
+ else NullReporter(),
51
+ )
52
+ return _ExecutionResult(_PlyData(mesh_data))