kompas-kernel 0.0.1__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.
- kompas_kernel/__init__.py +3 -0
- kompas_kernel/features/__init__.py +9 -0
- kompas_kernel/features/assembly_navigator/__init__.py +8 -0
- kompas_kernel/features/assembly_navigator/commands.py +37 -0
- kompas_kernel/features/assembly_navigator/ksapi/__init__.py +5 -0
- kompas_kernel/features/assembly_navigator/ksapi/mapping.py +122 -0
- kompas_kernel/features/assembly_navigator/ksapi/presentation.py +107 -0
- kompas_kernel/features/assembly_navigator/ksapi/raw.py +74 -0
- kompas_kernel/features/assembly_navigator/ksapi/reader.py +287 -0
- kompas_kernel/features/assembly_navigator/ksapi/resolver.py +35 -0
- kompas_kernel/features/assembly_navigator/models.py +218 -0
- kompas_kernel/features/assembly_navigator/queries.py +42 -0
- kompas_kernel/features/assembly_navigator/service.py +31 -0
- kompas_kernel/features/assembly_navigator/summary.py +90 -0
- kompas_kernel/features/document_session/__init__.py +5 -0
- kompas_kernel/features/document_session/checkpointed.py +165 -0
- kompas_kernel/features/document_session/commands.py +94 -0
- kompas_kernel/features/document_session/digest.py +126 -0
- kompas_kernel/features/document_session/ksapi/__init__.py +5 -0
- kompas_kernel/features/document_session/ksapi/context_reader.py +174 -0
- kompas_kernel/features/document_session/ksapi/screenshot.py +365 -0
- kompas_kernel/features/document_session/ksapi/snapshot.py +181 -0
- kompas_kernel/features/document_session/models.py +311 -0
- kompas_kernel/features/document_session/queries.py +62 -0
- kompas_kernel/features/document_session/registry.py +101 -0
- kompas_kernel/features/document_session/service.py +128 -0
- kompas_kernel/features/geometry_3d/__init__.py +9 -0
- kompas_kernel/features/geometry_3d/axis_math.py +50 -0
- kompas_kernel/features/geometry_3d/commands.py +68 -0
- kompas_kernel/features/geometry_3d/hex_detect.py +113 -0
- kompas_kernel/features/geometry_3d/ksapi/__init__.py +5 -0
- kompas_kernel/features/geometry_3d/ksapi/component_geometry.py +559 -0
- kompas_kernel/features/geometry_3d/ksapi/measurements.py +368 -0
- kompas_kernel/features/geometry_3d/ksapi/presentation.py +204 -0
- kompas_kernel/features/geometry_3d/models.py +216 -0
- kompas_kernel/features/geometry_3d/queries.py +96 -0
- kompas_kernel/features/geometry_3d/service.py +69 -0
- kompas_kernel/features/geometry_3d/wrench_zone.py +161 -0
- kompas_kernel/features/ksapi_automation/__init__.py +9 -0
- kompas_kernel/features/ksapi_automation/gate.py +282 -0
- kompas_kernel/features/ksapi_automation/models.py +75 -0
- kompas_kernel/features/ksapi_automation/namespace.py +12 -0
- kompas_kernel/features/ksapi_automation/run_python.py +133 -0
- kompas_kernel/features/ksapi_automation/runner.py +126 -0
- kompas_kernel/features/ksapi_automation/service.py +85 -0
- kompas_kernel/features/part_authoring/__init__.py +5 -0
- kompas_kernel/features/part_authoring/build_geometry.py +122 -0
- kompas_kernel/features/part_authoring/models.py +99 -0
- kompas_kernel/features/wrench_clearance/__init__.py +7 -0
- kompas_kernel/features/wrench_clearance/models.py +87 -0
- kompas_kernel/features/wrench_clearance/service.py +154 -0
- kompas_kernel/kompas/__init__.py +14 -0
- kompas_kernel/kompas/errors.py +11 -0
- kompas_kernel/kompas/models.py +68 -0
- kompas_kernel/kompas/objects.py +93 -0
- kompas_kernel/kompas/platform.py +40 -0
- kompas_kernel/kompas/runtime.py +186 -0
- kompas_kernel/kompas/session.py +195 -0
- kompas_kernel/kompas/units.py +48 -0
- kompas_kernel/ksapi_static/__init__.py +8 -0
- kompas_kernel/ksapi_static/facts.py +132 -0
- kompas_kernel/ksapi_static/inventory.py +185 -0
- kompas_kernel/ksapi_static/recipes.py +114 -0
- kompas_kernel/ksapi_static/wrapper_grep.py +83 -0
- kompas_kernel/observability/__init__.py +1 -0
- kompas_kernel/observability/logger.py +24 -0
- kompas_kernel/observability/setup.py +115 -0
- kompas_kernel/observability/taxonomy.py +27 -0
- kompas_kernel/py.typed +1 -0
- kompas_kernel/standards/__init__.py +1 -0
- kompas_kernel/standards/wrench_clearance.py +234 -0
- kompas_kernel/utils/__init__.py +1 -0
- kompas_kernel/utils/filesystem.py +82 -0
- kompas_kernel/utils/validation.py +21 -0
- kompas_kernel-0.0.1.dist-info/METADATA +56 -0
- kompas_kernel-0.0.1.dist-info/RECORD +77 -0
- kompas_kernel-0.0.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Framework-free capability slices for live work with a KOMPAS document/assembly.
|
|
2
|
+
|
|
3
|
+
Kernel owns the framework-free feature slices that talk to a live `KompasSession`
|
|
4
|
+
(`document_session`, `geometry_3d`, `assembly_navigator`, `wrench_clearance`) and the
|
|
5
|
+
`run_python` sandbox-policy slices (`ksapi_automation`, `part_authoring`, E17.04e): DTO,
|
|
6
|
+
queries/commands, gate/namespace policy and their own `ksapi/` implementation, with no
|
|
7
|
+
Pydantic AI and no Copilot import. Agent wiring, tool surface and model-facing text stay
|
|
8
|
+
in Copilot (`kompas_copilot.core.capabilities`).
|
|
9
|
+
"""
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Assembly navigator feature slice: read + show top-level КОМПАС-Сборка components.
|
|
2
|
+
|
|
3
|
+
Framework-free (no Pydantic AI, no `kompas_copilot.bridge`): owns its DTO (`models.py`),
|
|
4
|
+
raw KsAPI reader/resolver/presentation (`ksapi/`), public query/command API
|
|
5
|
+
(`queries.py`/`commands.py`) over a `KompasSession`, and a thin service holder
|
|
6
|
+
(`service.py`). Thin Pydantic AI registration lives in
|
|
7
|
+
`core/capabilities/assembly_navigator.py`.
|
|
8
|
+
"""
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Framework-free public show/select command for the assembly navigator.
|
|
2
|
+
|
|
3
|
+
No Pydantic AI, no `kompas_copilot.bridge` import — raw KsAPI select+zoom lives in
|
|
4
|
+
`ksapi/presentation.py`; this module only validates the empty-list precondition and
|
|
5
|
+
wires it through one session callback.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from kompas_kernel.features.assembly_navigator.ksapi.presentation import select_components_and_zoom
|
|
11
|
+
from kompas_kernel.features.assembly_navigator.models import ComponentIdentifiers, ShowComponentsResult
|
|
12
|
+
from kompas_kernel.kompas.errors import KompasError
|
|
13
|
+
from kompas_kernel.kompas.session import KompasContext, KompasSession
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def show_components(session: KompasSession, identifiers: ComponentIdentifiers) -> ShowComponentsResult:
|
|
17
|
+
"""Выделить компоненты и зумнуть вид по общему выделению (И3).
|
|
18
|
+
|
|
19
|
+
`identifiers` — непустой список индексов из `read_assembly`
|
|
20
|
+
(поле `AssemblyComponent.identifier`). Для одного компонента передаётся
|
|
21
|
+
список из одного элемента.
|
|
22
|
+
|
|
23
|
+
Все найденные цели передаются одним списком в `ISelectionManager.Select`, после
|
|
24
|
+
чего `ksCMZoomSelected` вызывается ровно один раз. Невалидный индекс отражается
|
|
25
|
+
как `found=False` и не мешает показать остальные найденные компоненты.
|
|
26
|
+
"""
|
|
27
|
+
if not identifiers:
|
|
28
|
+
raise KompasError("show_component: передан пустой список идентификаторов")
|
|
29
|
+
identifier_snapshot = list(identifiers)
|
|
30
|
+
|
|
31
|
+
def _select(context: KompasContext) -> ShowComponentsResult:
|
|
32
|
+
return select_components_and_zoom(context.modules, context.app, identifier_snapshot)
|
|
33
|
+
|
|
34
|
+
return await session.run(
|
|
35
|
+
operation_name="assembly_navigator.show_component",
|
|
36
|
+
operation=_select,
|
|
37
|
+
)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Pure RawAssembly -> AssemblyView mapping without live KsAPI calls.
|
|
2
|
+
|
|
3
|
+
Перенесено из `bridge/kompas_ksapi/pure/assembly.py` (E16.04) без изменения поведения.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from kompas_kernel.features.assembly_navigator.ksapi.raw import (
|
|
9
|
+
RawAssembly,
|
|
10
|
+
RawComponent,
|
|
11
|
+
RawStandardComponentInfo,
|
|
12
|
+
RawStandardReferenceObject,
|
|
13
|
+
)
|
|
14
|
+
from kompas_kernel.features.assembly_navigator.models import (
|
|
15
|
+
AssemblyComponent,
|
|
16
|
+
AssemblyView,
|
|
17
|
+
ComponentKind,
|
|
18
|
+
ComponentLoadState,
|
|
19
|
+
ComponentPlacement,
|
|
20
|
+
StandardComponentAxis,
|
|
21
|
+
StandardComponentInfo,
|
|
22
|
+
StandardComponentPlane,
|
|
23
|
+
StandardReferenceObject,
|
|
24
|
+
)
|
|
25
|
+
from kompas_kernel.kompas.models import BBox, Point3D
|
|
26
|
+
from kompas_kernel.kompas.objects import Gabarit
|
|
27
|
+
|
|
28
|
+
# Маппинг ksLoadStateEnum (ksConstants.h) -> ComponentLoadState.
|
|
29
|
+
# Кодировка: -1 = недоступно/неизвестно, 0 = полная загрузка, 1 = выгружен,
|
|
30
|
+
# 2 = только треугольники, 3 = частичная загрузка, 4 = только габарит.
|
|
31
|
+
# Неизвестный код (не перечислен ниже) -> UNKNOWN.
|
|
32
|
+
_LOAD_STATE_MAP: dict[int, ComponentLoadState] = {
|
|
33
|
+
-1: ComponentLoadState.UNKNOWN,
|
|
34
|
+
0: ComponentLoadState.FULL,
|
|
35
|
+
1: ComponentLoadState.UNLOADED,
|
|
36
|
+
2: ComponentLoadState.TRIANGLES,
|
|
37
|
+
3: ComponentLoadState.PARTIAL,
|
|
38
|
+
4: ComponentLoadState.GABARIT,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def build_assembly_view(raw: RawAssembly) -> AssemblyView:
|
|
43
|
+
"""`RawAssembly` -> `AssemblyView`: маппит сырые поля в DTO без КОМПАС.
|
|
44
|
+
|
|
45
|
+
Не делает запросов к KsAPI - полностью детерминирован и тестируем без КОМПАС.
|
|
46
|
+
"""
|
|
47
|
+
components = [_build_component(rc) for rc in raw.components]
|
|
48
|
+
return AssemblyView(
|
|
49
|
+
is_assembly=raw.is_assembly,
|
|
50
|
+
top_marking=raw.top_marking,
|
|
51
|
+
components=components,
|
|
52
|
+
component_count=len(components),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _build_component(rc: RawComponent) -> AssemblyComponent:
|
|
57
|
+
"""Маппинг одного RawComponent -> AssemblyComponent (порт DTO)."""
|
|
58
|
+
bbox = None if rc.gabarit_mm is None else _bbox_from_gabarit(rc.gabarit_mm)
|
|
59
|
+
load_state = _LOAD_STATE_MAP.get(rc.load_state_code, ComponentLoadState.UNKNOWN)
|
|
60
|
+
kind = ComponentKind.PART if rc.is_detail else ComponentKind.SUBASSEMBLY
|
|
61
|
+
placement = ComponentPlacement(
|
|
62
|
+
origin_x=rc.origin_x,
|
|
63
|
+
origin_y=rc.origin_y,
|
|
64
|
+
origin_z=rc.origin_z,
|
|
65
|
+
)
|
|
66
|
+
return AssemblyComponent(
|
|
67
|
+
identifier=rc.identifier,
|
|
68
|
+
marking=rc.marking,
|
|
69
|
+
name=rc.name,
|
|
70
|
+
kind=kind,
|
|
71
|
+
is_standard=rc.is_standard,
|
|
72
|
+
placement=placement,
|
|
73
|
+
bbox=bbox,
|
|
74
|
+
standard=_build_standard_info(rc.standard),
|
|
75
|
+
load_state=load_state,
|
|
76
|
+
visible=rc.visible,
|
|
77
|
+
valid=rc.valid,
|
|
78
|
+
excluded=rc.excluded,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _build_standard_info(raw: RawStandardComponentInfo | None) -> StandardComponentInfo | None:
|
|
83
|
+
if raw is None:
|
|
84
|
+
return None
|
|
85
|
+
axis = None
|
|
86
|
+
if raw.axis_origin is not None and raw.axis_direction is not None:
|
|
87
|
+
axis = StandardComponentAxis(
|
|
88
|
+
origin=_point_from_tuple(raw.axis_origin),
|
|
89
|
+
direction=_point_from_tuple(raw.axis_direction),
|
|
90
|
+
reference=_build_standard_reference(raw.axis_reference),
|
|
91
|
+
)
|
|
92
|
+
plane = None
|
|
93
|
+
if raw.plane_origin is not None and raw.plane_normal is not None:
|
|
94
|
+
plane = StandardComponentPlane(
|
|
95
|
+
origin=_point_from_tuple(raw.plane_origin),
|
|
96
|
+
normal=_point_from_tuple(raw.plane_normal),
|
|
97
|
+
reference=_build_standard_reference(raw.plane_reference),
|
|
98
|
+
)
|
|
99
|
+
return StandardComponentInfo(file_name=raw.file_name, axis=axis, plane=plane)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _build_standard_reference(raw: RawStandardReferenceObject | None) -> StandardReferenceObject | None:
|
|
103
|
+
if raw is None:
|
|
104
|
+
return None
|
|
105
|
+
return StandardReferenceObject(name=raw.name, model_object_type=raw.model_object_type, source=raw.source)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _point_from_tuple(point: tuple[float, float, float]) -> Point3D:
|
|
109
|
+
return Point3D(x_mm=point[0], y_mm=point[1], z_mm=point[2])
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _bbox_from_gabarit(gabarit: Gabarit) -> BBox:
|
|
113
|
+
"""Кортеж (x1, y1, z1, x2, y2, z2) -> BBox с нормализованными min/max."""
|
|
114
|
+
x1, y1, z1, x2, y2, z2 = gabarit
|
|
115
|
+
return BBox(
|
|
116
|
+
min_x=min(x1, x2),
|
|
117
|
+
min_y=min(y1, y2),
|
|
118
|
+
min_z=min(z1, z2),
|
|
119
|
+
max_x=max(x1, x2),
|
|
120
|
+
max_y=max(y1, y2),
|
|
121
|
+
max_z=max(z1, z2),
|
|
122
|
+
)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Live KsAPI select+zoom for one or more assembly components.
|
|
2
|
+
|
|
3
|
+
Перенесено дословно из `KompasKsApiAdapter._select_components_and_zoom`
|
|
4
|
+
(`bridge/kompas_ksapi/adapter.py:497-578`, E16.04): один `Select` на все найденные цели,
|
|
5
|
+
один `ksCMZoomSelected` c `async=True`, тот же лог-эвент
|
|
6
|
+
`bridge.ksapi_show_component.completed`. Индекс резолвится тем же `resolver.py`, что и
|
|
7
|
+
`reader.py` — устраняет прежнее раздвоение numbering/parsing между чтением и показом.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from kompas_kernel.features.assembly_navigator.ksapi.resolver import list_components, resolve_index
|
|
15
|
+
from kompas_kernel.features.assembly_navigator.models import ComponentShowResult, ShowComponentsResult
|
|
16
|
+
from kompas_kernel.kompas.errors import KompasError
|
|
17
|
+
from kompas_kernel.kompas.objects import as_document_3d, constants_class
|
|
18
|
+
from kompas_kernel.kompas.runtime import KsApiModules
|
|
19
|
+
from kompas_kernel.observability.logger import get_logger
|
|
20
|
+
from kompas_kernel.observability.taxonomy import LogDomain, LogEventType, event_name
|
|
21
|
+
|
|
22
|
+
logger = get_logger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def select_components_and_zoom(modules: KsApiModules, app: Any, identifiers: list[str]) -> ShowComponentsResult:
|
|
26
|
+
"""Выделить найденные компоненты одним `Select`, затем зумнуть одной командой.
|
|
27
|
+
|
|
28
|
+
Невалидный индекс отражается как `found=False` у соответствующего результата и не
|
|
29
|
+
мешает показать остальные найденные компоненты.
|
|
30
|
+
"""
|
|
31
|
+
doc3d = as_document_3d(modules, app.GetActiveDocument())
|
|
32
|
+
if doc3d is None:
|
|
33
|
+
raise KompasError("show_component: активный документ не 3D")
|
|
34
|
+
constants = constants_class(modules)
|
|
35
|
+
top = doc3d.GetTopPart()
|
|
36
|
+
components = list_components(modules, top)
|
|
37
|
+
|
|
38
|
+
resolutions: list[tuple[str, int | None]] = []
|
|
39
|
+
target_indices: list[int] = []
|
|
40
|
+
seen_indices: set[int] = set()
|
|
41
|
+
for requested_identifier in identifiers:
|
|
42
|
+
resolved_index = resolve_index(components, requested_identifier)
|
|
43
|
+
resolutions.append((requested_identifier, resolved_index))
|
|
44
|
+
if resolved_index is not None and resolved_index not in seen_indices:
|
|
45
|
+
seen_indices.add(resolved_index)
|
|
46
|
+
target_indices.append(resolved_index)
|
|
47
|
+
|
|
48
|
+
if not target_indices:
|
|
49
|
+
logger.info(
|
|
50
|
+
event_name(LogDomain.BRIDGE, "ksapi_show_component", LogEventType.COMPLETED),
|
|
51
|
+
identifiers=identifiers,
|
|
52
|
+
requested_count=len(identifiers),
|
|
53
|
+
found_count=0,
|
|
54
|
+
selected_count=0,
|
|
55
|
+
zoomed=False,
|
|
56
|
+
)
|
|
57
|
+
return ShowComponentsResult(
|
|
58
|
+
components=tuple(
|
|
59
|
+
ComponentShowResult(identifier=value, found=False, selected=False, marking="")
|
|
60
|
+
for value, _index in resolutions
|
|
61
|
+
),
|
|
62
|
+
zoomed=False,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
targets = [components[index] for index in target_indices]
|
|
66
|
+
sm = doc3d.GetSelectionManager()
|
|
67
|
+
sm.UnselectAll()
|
|
68
|
+
sm.Select(targets)
|
|
69
|
+
selected_by_index = {
|
|
70
|
+
index: bool(sm.IsSelected(target)) for index, target in zip(target_indices, targets, strict=True)
|
|
71
|
+
}
|
|
72
|
+
all_found_selected = all(selected_by_index.values())
|
|
73
|
+
# Зум по выделению: ExecuteKompasCommand требует async=True — sync с worker-потока
|
|
74
|
+
# не отрабатывает (ksCMZoomSelected = 32416). См. docs/recipes/ksapi-link.md.
|
|
75
|
+
zoomed = all_found_selected and bool(app.ExecuteKompasCommand(int(constants.ksCMZoomSelected), True))
|
|
76
|
+
|
|
77
|
+
results: list[ComponentShowResult] = []
|
|
78
|
+
for requested_identifier, index in resolutions:
|
|
79
|
+
if index is None:
|
|
80
|
+
results.append(
|
|
81
|
+
ComponentShowResult(
|
|
82
|
+
identifier=requested_identifier,
|
|
83
|
+
found=False,
|
|
84
|
+
selected=False,
|
|
85
|
+
marking="",
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
continue
|
|
89
|
+
marking_raw = components[index].GetMarking()
|
|
90
|
+
results.append(
|
|
91
|
+
ComponentShowResult(
|
|
92
|
+
identifier=requested_identifier,
|
|
93
|
+
found=True,
|
|
94
|
+
selected=selected_by_index[index],
|
|
95
|
+
marking=str(marking_raw) if marking_raw is not None else "",
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
logger.info(
|
|
100
|
+
event_name(LogDomain.BRIDGE, "ksapi_show_component", LogEventType.COMPLETED),
|
|
101
|
+
identifiers=identifiers,
|
|
102
|
+
requested_count=len(identifiers),
|
|
103
|
+
found_count=sum(index is not None for _identifier, index in resolutions),
|
|
104
|
+
selected_count=sum(selected_by_index[index] for _identifier, index in resolutions if index is not None),
|
|
105
|
+
zoomed=zoomed,
|
|
106
|
+
)
|
|
107
|
+
return ShowComponentsResult(components=tuple(results), zoomed=zoomed)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Raw assembly facts read from KsAPI before mapping to public DTOs.
|
|
2
|
+
|
|
3
|
+
Перенесено из `bridge/kompas_ksapi/raw/assembly.py` (E16.04). `Gabarit` теперь живёт в
|
|
4
|
+
`kompas_kernel.kompas.objects` (общий примитив, используется и вне assembly navigator).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
from kompas_kernel.features.assembly_navigator.models import StandardReferenceSource
|
|
12
|
+
from kompas_kernel.kompas.objects import Gabarit
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class RawStandardReferenceObject:
|
|
17
|
+
"""Сырой факт о найденном опорном объекте стандартного компонента."""
|
|
18
|
+
|
|
19
|
+
name: str
|
|
20
|
+
model_object_type: int
|
|
21
|
+
source: StandardReferenceSource
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class RawStandardComponentInfo:
|
|
26
|
+
"""Сырые дополнительные поля для `IPart.IsStandard()==True`."""
|
|
27
|
+
|
|
28
|
+
file_name: str | None
|
|
29
|
+
axis_origin: tuple[float, float, float] | None
|
|
30
|
+
axis_direction: tuple[float, float, float] | None
|
|
31
|
+
axis_reference: RawStandardReferenceObject | None
|
|
32
|
+
plane_origin: tuple[float, float, float] | None
|
|
33
|
+
plane_normal: tuple[float, float, float] | None
|
|
34
|
+
plane_reference: RawStandardReferenceObject | None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class RawComponent:
|
|
39
|
+
"""Сырые поля одного компонента (IPart) из GetPartsArray - до маппинга в DTO.
|
|
40
|
+
|
|
41
|
+
`identifier` - строковый индекс в массиве GetPartsArray, стабилен в сессии.
|
|
42
|
+
`name` - None, если GetName() вернул пустую строку (нормализуется reader'ом).
|
|
43
|
+
`gabarit_mm` - (x1, y1, z1, x2, y2, z2) в мм; None если нет тела/габарита.
|
|
44
|
+
`load_state_code` - сырое целое из GetLoadState() (ksLoadStateEnum).
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
identifier: str
|
|
48
|
+
marking: str
|
|
49
|
+
name: str | None
|
|
50
|
+
is_standard: bool
|
|
51
|
+
standard: RawStandardComponentInfo | None
|
|
52
|
+
is_detail: bool
|
|
53
|
+
origin_x: float
|
|
54
|
+
origin_y: float
|
|
55
|
+
origin_z: float
|
|
56
|
+
gabarit_mm: Gabarit | None
|
|
57
|
+
load_state_code: int
|
|
58
|
+
visible: bool
|
|
59
|
+
valid: bool
|
|
60
|
+
excluded: bool
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(frozen=True)
|
|
64
|
+
class RawAssembly:
|
|
65
|
+
"""Сырой снимок верхнего уровня сборки/детали - до маппинга в AssemblyView.
|
|
66
|
+
|
|
67
|
+
`is_assembly` - True если GetDocumentType() == ksDocumentAssembly (5).
|
|
68
|
+
`top_marking` - обозначение головного Part из GetMarking().
|
|
69
|
+
`components` - список сырых компонентов верхнего уровня (все вставки, дубли сохранены).
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
is_assembly: bool
|
|
73
|
+
top_marking: str
|
|
74
|
+
components: list[RawComponent]
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""Live KsAPI reader for top-level assembly components and standard-component facts.
|
|
2
|
+
|
|
3
|
+
Перенесено из `bridge/kompas_ksapi/api/assembly_reader.py` + `api/standard_parts.py`
|
|
4
|
+
(E16.04, слиты в один файл — `standard_parts.py` был единственным потребителем
|
|
5
|
+
`read_raw_standard_component`). Компонентная нумерация и резолв индекса теперь общие
|
|
6
|
+
с `presentation.py` через `resolver.py` (устраняет прежнее раздвоение `enumerate` vs
|
|
7
|
+
`_parse_index`), поведение и семантика не изменились.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections.abc import Callable
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from kompas_kernel.features.assembly_navigator.ksapi.raw import (
|
|
16
|
+
RawAssembly,
|
|
17
|
+
RawComponent,
|
|
18
|
+
RawStandardComponentInfo,
|
|
19
|
+
RawStandardReferenceObject,
|
|
20
|
+
)
|
|
21
|
+
from kompas_kernel.features.assembly_navigator.ksapi.resolver import component_identifier, list_components
|
|
22
|
+
from kompas_kernel.features.assembly_navigator.models import StandardReferenceSource
|
|
23
|
+
from kompas_kernel.kompas.errors import KompasError
|
|
24
|
+
from kompas_kernel.kompas.objects import as_document_3d, constants3d_class, constants_class, part_gabarit
|
|
25
|
+
from kompas_kernel.kompas.runtime import KsApiModules
|
|
26
|
+
from kompas_kernel.observability.logger import get_logger
|
|
27
|
+
from kompas_kernel.observability.taxonomy import LogDomain, LogEventType, event_name
|
|
28
|
+
|
|
29
|
+
logger = get_logger(__name__)
|
|
30
|
+
|
|
31
|
+
_GET_VECTOR_VALUE_COUNT = 4
|
|
32
|
+
_STANDARD_AXIS_NAMES = ("Axis",)
|
|
33
|
+
_STANDARD_PLANE_NAMES = ("Plane", "Plain")
|
|
34
|
+
_UNNAMED_REFERENCE_NAME = "<unnamed>"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def read_raw_assembly(modules: KsApiModules, app: Any) -> RawAssembly:
|
|
38
|
+
doc = app.GetActiveDocument()
|
|
39
|
+
doc3d = as_document_3d(modules, doc)
|
|
40
|
+
if doc3d is None:
|
|
41
|
+
raise KompasError("read_assembly: активный документ не 3D")
|
|
42
|
+
constants = constants_class(modules)
|
|
43
|
+
constants3d = constants3d_class(modules)
|
|
44
|
+
is_assembly: bool = bool(doc.GetDocumentType() == constants.ksDocumentAssembly)
|
|
45
|
+
top = doc3d.GetTopPart()
|
|
46
|
+
parts_list = list_components(modules, top)
|
|
47
|
+
components = [_read_raw_component(constants3d=constants3d, part=p, index=i) for i, p in enumerate(parts_list)]
|
|
48
|
+
top_marking_raw = top.GetMarking()
|
|
49
|
+
top_marking = str(top_marking_raw) if top_marking_raw is not None else ""
|
|
50
|
+
return RawAssembly(
|
|
51
|
+
is_assembly=is_assembly,
|
|
52
|
+
top_marking=top_marking,
|
|
53
|
+
components=components,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _read_raw_component(*, constants3d: Any, part: Any, index: int) -> RawComponent:
|
|
58
|
+
"""Прочитать поля одного компонента (IPart) с worker-потока -> RawComponent."""
|
|
59
|
+
identifier = component_identifier(index)
|
|
60
|
+
marking = str(part.GetMarking() or "")
|
|
61
|
+
raw_name = str(part.GetName() or "")
|
|
62
|
+
name: str | None = raw_name or None
|
|
63
|
+
is_standard = bool(part.IsStandard())
|
|
64
|
+
is_detail = bool(part.IsDetail())
|
|
65
|
+
|
|
66
|
+
ox: float = 0.0
|
|
67
|
+
oy: float = 0.0
|
|
68
|
+
oz: float = 0.0
|
|
69
|
+
axis_direction: tuple[float, float, float] | None = None
|
|
70
|
+
try:
|
|
71
|
+
pl = part.GetPlacement()
|
|
72
|
+
# GetOrigin - out-параметры: возвращает (ok, x, y, z), а не голую тройку.
|
|
73
|
+
origin = pl.GetOrigin(0.0, 0.0, 0.0)
|
|
74
|
+
ox = float(origin[1])
|
|
75
|
+
oy = float(origin[2])
|
|
76
|
+
oz = float(origin[3])
|
|
77
|
+
# GetMatrix3D() (16 float, 4x4) не читаем: её не потреблял никто, кроме выдачи
|
|
78
|
+
# модели, а там она стоила ~450 байт на компонент. Ориентация стандартного
|
|
79
|
+
# изделия берётся ниже из GetVector(o3d_axisOZ) - точнее и дешевле.
|
|
80
|
+
except Exception as exc:
|
|
81
|
+
logger.warning(
|
|
82
|
+
event_name(LogDomain.BRIDGE, "ksapi_read_component_placement", LogEventType.FAILED),
|
|
83
|
+
identifier=identifier,
|
|
84
|
+
error=str(exc),
|
|
85
|
+
)
|
|
86
|
+
else:
|
|
87
|
+
axis_direction = _placement_axis_direction(
|
|
88
|
+
placement=pl,
|
|
89
|
+
constants3d=constants3d,
|
|
90
|
+
identifier=identifier,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
gabarit_mm = part_gabarit(part, body_count=1)
|
|
94
|
+
load_state_code = int(part.GetLoadState())
|
|
95
|
+
visible = bool(part.IsStaffVisible())
|
|
96
|
+
valid = bool(part.IsValid())
|
|
97
|
+
excluded = bool(part.IsInheritExclude())
|
|
98
|
+
standard = (
|
|
99
|
+
read_raw_standard_component(
|
|
100
|
+
constants3d=constants3d,
|
|
101
|
+
part=part,
|
|
102
|
+
identifier=identifier,
|
|
103
|
+
origin=(ox, oy, oz),
|
|
104
|
+
axis_direction=axis_direction,
|
|
105
|
+
)
|
|
106
|
+
if is_standard
|
|
107
|
+
else None
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return RawComponent(
|
|
111
|
+
identifier=identifier,
|
|
112
|
+
marking=marking,
|
|
113
|
+
name=name,
|
|
114
|
+
is_standard=is_standard,
|
|
115
|
+
standard=standard,
|
|
116
|
+
is_detail=is_detail,
|
|
117
|
+
origin_x=ox,
|
|
118
|
+
origin_y=oy,
|
|
119
|
+
origin_z=oz,
|
|
120
|
+
gabarit_mm=gabarit_mm,
|
|
121
|
+
load_state_code=load_state_code,
|
|
122
|
+
visible=visible,
|
|
123
|
+
valid=valid,
|
|
124
|
+
excluded=excluded,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _placement_axis_direction(
|
|
129
|
+
*,
|
|
130
|
+
placement: Any,
|
|
131
|
+
constants3d: Any,
|
|
132
|
+
identifier: str,
|
|
133
|
+
) -> tuple[float, float, float] | None:
|
|
134
|
+
try:
|
|
135
|
+
vector = placement.GetVector(int(constants3d.o3d_axisOZ), 0.0, 0.0, 0.0)
|
|
136
|
+
except Exception as exc:
|
|
137
|
+
logger.warning(
|
|
138
|
+
event_name(LogDomain.BRIDGE, "ksapi_read_component_axis", LogEventType.FAILED),
|
|
139
|
+
identifier=identifier,
|
|
140
|
+
error=str(exc),
|
|
141
|
+
)
|
|
142
|
+
return None
|
|
143
|
+
if vector is None:
|
|
144
|
+
return None
|
|
145
|
+
try:
|
|
146
|
+
values = tuple(vector)
|
|
147
|
+
except TypeError:
|
|
148
|
+
logger.warning(
|
|
149
|
+
event_name(LogDomain.BRIDGE, "ksapi_read_component_axis", LogEventType.FAILED),
|
|
150
|
+
identifier=identifier,
|
|
151
|
+
error=f"unexpected GetVector result: {vector!r}",
|
|
152
|
+
)
|
|
153
|
+
return None
|
|
154
|
+
if len(values) < _GET_VECTOR_VALUE_COUNT or not bool(values[0]):
|
|
155
|
+
return None
|
|
156
|
+
return (float(values[1]), float(values[2]), float(values[3]))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def read_raw_standard_component(
|
|
160
|
+
*,
|
|
161
|
+
constants3d: Any,
|
|
162
|
+
part: Any,
|
|
163
|
+
identifier: str,
|
|
164
|
+
origin: tuple[float, float, float],
|
|
165
|
+
axis_direction: tuple[float, float, float] | None,
|
|
166
|
+
) -> RawStandardComponentInfo:
|
|
167
|
+
file_name = _read_optional_string(
|
|
168
|
+
part.GetFileName,
|
|
169
|
+
identifier=identifier,
|
|
170
|
+
field="file_name",
|
|
171
|
+
)
|
|
172
|
+
axis_reference = _read_standard_reference(
|
|
173
|
+
constants3d=constants3d,
|
|
174
|
+
part=part,
|
|
175
|
+
identifier=identifier,
|
|
176
|
+
role="axis",
|
|
177
|
+
named_candidates=_STANDARD_AXIS_NAMES,
|
|
178
|
+
default_object_type=int(constants3d.o3d_axisOZ),
|
|
179
|
+
)
|
|
180
|
+
plane_reference = _read_standard_reference(
|
|
181
|
+
constants3d=constants3d,
|
|
182
|
+
part=part,
|
|
183
|
+
identifier=identifier,
|
|
184
|
+
role="plane",
|
|
185
|
+
named_candidates=_STANDARD_PLANE_NAMES,
|
|
186
|
+
default_object_type=int(constants3d.o3d_planeXOY),
|
|
187
|
+
)
|
|
188
|
+
return RawStandardComponentInfo(
|
|
189
|
+
file_name=file_name,
|
|
190
|
+
axis_origin=origin if axis_direction is not None else None,
|
|
191
|
+
axis_direction=axis_direction,
|
|
192
|
+
axis_reference=axis_reference,
|
|
193
|
+
plane_origin=origin if axis_direction is not None else None,
|
|
194
|
+
plane_normal=axis_direction,
|
|
195
|
+
plane_reference=plane_reference,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _read_optional_string(read: Callable[[], object], *, identifier: str, field: str) -> str | None:
|
|
200
|
+
try:
|
|
201
|
+
raw = read()
|
|
202
|
+
except Exception as exc:
|
|
203
|
+
logger.warning(
|
|
204
|
+
event_name(LogDomain.BRIDGE, "ksapi_read_component_field", LogEventType.FAILED),
|
|
205
|
+
identifier=identifier,
|
|
206
|
+
field=field,
|
|
207
|
+
error=str(exc),
|
|
208
|
+
)
|
|
209
|
+
return None
|
|
210
|
+
value = str(raw or "")
|
|
211
|
+
return value or None
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _read_standard_reference(
|
|
215
|
+
*,
|
|
216
|
+
constants3d: Any,
|
|
217
|
+
part: Any,
|
|
218
|
+
identifier: str,
|
|
219
|
+
role: str,
|
|
220
|
+
named_candidates: tuple[str, ...],
|
|
221
|
+
default_object_type: int,
|
|
222
|
+
) -> RawStandardReferenceObject | None:
|
|
223
|
+
unknown_type = int(constants3d.o3d_unknown)
|
|
224
|
+
for name in named_candidates:
|
|
225
|
+
try:
|
|
226
|
+
obj = part.GetObjectByName(name, unknown_type, True, True)
|
|
227
|
+
except Exception as exc:
|
|
228
|
+
logger.warning(
|
|
229
|
+
event_name(LogDomain.BRIDGE, "ksapi_read_standard_reference", LogEventType.FAILED),
|
|
230
|
+
identifier=identifier,
|
|
231
|
+
role=role,
|
|
232
|
+
source=StandardReferenceSource.NAMED.value,
|
|
233
|
+
name=name,
|
|
234
|
+
error=str(exc),
|
|
235
|
+
)
|
|
236
|
+
continue
|
|
237
|
+
try:
|
|
238
|
+
reference = _standard_reference_from_object(obj, StandardReferenceSource.NAMED)
|
|
239
|
+
except Exception as exc:
|
|
240
|
+
logger.warning(
|
|
241
|
+
event_name(LogDomain.BRIDGE, "ksapi_read_standard_reference", LogEventType.FAILED),
|
|
242
|
+
identifier=identifier,
|
|
243
|
+
role=role,
|
|
244
|
+
source=StandardReferenceSource.NAMED.value,
|
|
245
|
+
name=name,
|
|
246
|
+
error=str(exc),
|
|
247
|
+
)
|
|
248
|
+
continue
|
|
249
|
+
if reference is not None:
|
|
250
|
+
return reference
|
|
251
|
+
try:
|
|
252
|
+
obj = part.GetDefaultObject(default_object_type)
|
|
253
|
+
except Exception as exc:
|
|
254
|
+
logger.warning(
|
|
255
|
+
event_name(LogDomain.BRIDGE, "ksapi_read_standard_reference", LogEventType.FAILED),
|
|
256
|
+
identifier=identifier,
|
|
257
|
+
role=role,
|
|
258
|
+
source=StandardReferenceSource.DEFAULT.value,
|
|
259
|
+
error=str(exc),
|
|
260
|
+
)
|
|
261
|
+
return None
|
|
262
|
+
try:
|
|
263
|
+
return _standard_reference_from_object(obj, StandardReferenceSource.DEFAULT)
|
|
264
|
+
except Exception as exc:
|
|
265
|
+
logger.warning(
|
|
266
|
+
event_name(LogDomain.BRIDGE, "ksapi_read_standard_reference", LogEventType.FAILED),
|
|
267
|
+
identifier=identifier,
|
|
268
|
+
role=role,
|
|
269
|
+
source=StandardReferenceSource.DEFAULT.value,
|
|
270
|
+
error=str(exc),
|
|
271
|
+
)
|
|
272
|
+
return None
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _standard_reference_from_object(
|
|
276
|
+
obj: Any | None,
|
|
277
|
+
source: StandardReferenceSource,
|
|
278
|
+
) -> RawStandardReferenceObject | None:
|
|
279
|
+
if obj is None:
|
|
280
|
+
return None
|
|
281
|
+
raw_name = str(obj.GetName() or "")
|
|
282
|
+
name = raw_name or _UNNAMED_REFERENCE_NAME
|
|
283
|
+
return RawStandardReferenceObject(
|
|
284
|
+
name=name,
|
|
285
|
+
model_object_type=int(obj.GetModelObjectType()),
|
|
286
|
+
source=source,
|
|
287
|
+
)
|